<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>강생강의 하루</title>
    <link>https://gingerkang.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 9 Jun 2026 10:59:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>생강강</managingEditor>
    <image>
      <title>강생강의 하루</title>
      <url>https://tistory1.daumcdn.net/tistory/3778734/attach/0bb182d6f3914837b6092093618676af</url>
      <link>https://gingerkang.tistory.com</link>
    </image>
    <item>
      <title>[GraphQL] 쿼리 실행 과정과 리졸버</title>
      <link>https://gingerkang.tistory.com/128</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TUGMW/btrfcvx5EQ0/YtYKwuyogikI6jxIf6hCGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TUGMW/btrfcvx5EQ0/YtYKwuyogikI6jxIf6hCGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TUGMW/btrfcvx5EQ0/YtYKwuyogikI6jxIf6hCGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTUGMW%2Fbtrfcvx5EQ0%2FYtYKwuyogikI6jxIf6hCGK%2Fimg.png&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;쿼리 실행 과정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;모든 GraphQL 쿼리는 &lt;span&gt;parsed, validated, executed&lt;/span&gt; 과정을 통해 실행이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 GraphQL 쿼리의 기본적인 형태는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631717704551&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;query GetUser($userId: ID!) {
  user(id: $userId) {
    id,
    name,
    isViewerFriend,
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 쿼리를 실행시키기 전에 파싱하여 AST(Abstract Syntax Tree)로 만드는 작업을 거친다. &lt;s&gt;Babel이나 EsLint도 해당 작업을 거친다.&lt;/s&gt; 파싱된 AST는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;938&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yMOJn/btre9EpvVQA/tSKQc8LklRiTh6Ky1LLuJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yMOJn/btre9EpvVQA/tSKQc8LklRiTh6Ky1LLuJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yMOJn/btre9EpvVQA/tSKQc8LklRiTh6Ky1LLuJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyMOJn%2Fbtre9EpvVQA%2FtSKQc8LklRiTh6Ky1LLuJk%2Fimg.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;938&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그 이후에는 해당 쿼리가 유효한지 validation 과정을 거친다. 스키마에서 정의된 필드가 없거나 잘못 됐을 경우 등을 검증하는 과정이다. 그리고 AST를 기반으로 트리의 루트부터 리졸버를 실행시킨다. 그리고 리졸버에서 리턴하는 결과물을 통해 JSON 형태로 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;리졸버&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;리졸버는 스키마에 작성한 타입의 필드 값들을 정의한다. 즉, 특정 필드의 데이터를 반환하는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스키마는 객체를 리턴할 수도 있고, &lt;b&gt;String&lt;/b&gt;, &lt;b&gt;Int&lt;/b&gt;, &lt;b&gt;Boolean&lt;/b&gt; 등과 같이 scalar 값들도 리턴할 수 있다. 객체가 리턴 될 경우에는 자식 필드(child field)로 체이닝 되면서 리졸버가 실행되고, scalar가 리턴 될 경우 실행이 완료된 것으로 간주한다. 또한 &lt;b&gt;null&lt;/b&gt;이 리턴 되어도 종료된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한 리졸버는 비동기적으로 실행이 되며, DB 등 여러 데이터 소스에 접근이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631714599791&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Query {
  user(id: ID!): User
}

type User {
  id: ID!
  age: Int
  name: String
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위와 같이 스키마가 정의되어 있다고 하자. 그리고 리졸버를 다음과 같이 작성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631715632160&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const resolvers = {
  Query: {
    user(parent, args, context, info) {
      return users.find(user =&amp;gt; user.id === args.id)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;가장 기본적인 리졸버의 형태이다. 리졸버 함수를 살펴보면 네 개의 인자를 받고 있는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이처럼 모든 리졸버는 &lt;b&gt;parent&lt;/b&gt;, &lt;b&gt;args&lt;/b&gt;, &lt;b&gt;context&lt;/b&gt;, &lt;b&gt;info&lt;/b&gt; 네 개의 인자를 받는다. &lt;b&gt;parent&lt;/b&gt;는 이전/부모 필드의 결과 값이 담겨 있고 이를 통해 체이닝이 일어난다. &lt;b&gt;args&lt;/b&gt;는 필드로 넘겨진 인자 값들이 담겨 있으며 주로 비구조화 할당을 통해 사용한다. &lt;b&gt;context&lt;/b&gt;는 모든 리졸버 함수에 제공되는 mutable한 객체가 담겨있다. 마지막으로 &lt;b&gt;info&lt;/b&gt;는 쿼리와 관련된 특정 필드에 대한 정보가 담겨있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Default Resolver&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1631716968042&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const users = [
  {
    id: '1',
    age: 21,
    name: 'mori nana',
  },
  {
    id: '2',
    age: 23,
    name: 'hirose suzu',
  }
];

const resolver = {
  Query: {
    user(parent, args, context, info) {
      return users.find(user =&amp;gt; user.id === args.id)
    }
  },
  User: {
    age(parent, args, context, info) {
      return parent.age
    },
    name(parent, args, context, info) {
      return parent.name
    },
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;GraphQL 서버는 모든 필드에 대해 default resolver를 만들어 둔다. default resolver는 &lt;b&gt;parent&lt;/b&gt;객체에서 필드와 같은 이름을 가진 프로퍼티를 찾아 리턴한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;리졸버 체인&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;공식 문서에 있는 예시를 보면서 이해를 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631717378237&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const libraries = [
  {
    branch: 'downtown'
  },
  {
    branch: 'riverside'
  },
];

const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
    branch: 'riverside'
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
    branch: 'downtown'
  },
];

const typeDefs = gql`
  type Library {
    branch: String!
    books: [Book!]
  }

  type Book {
    title: String!
    author: Author!
  }

  type Author {
    name: String!
  }

  type Query {
    libraries: [Library]
  }
`;

const resolvers = {
  Query: {
    libraries() {
      return libraries;
    }
  },
  Library: {
    books(parent) {
      return books.filter(book =&amp;gt; book.branch === parent.branch);
    }
  },
  Book: {
    author(parent) {
      return {
        name: parent.author
      };
    }
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;libraries&lt;/b&gt;, &lt;b&gt;books&lt;/b&gt;와 같이 필요한 데이터들은 하드코딩 했다. 스키마와 리졸버 위와 같은 상태에서 아래와 같은 쿼리를 실행시켜본다고 하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631717485856&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;query GetBooksByLibrary {
  libraries {
    books {
      author {
        name
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;쿼리가 실행이 되면 루트 쿼리에 대한 리졸버가 실행이 될 것이고, 위에서 &lt;b&gt;libraries&lt;/b&gt;가 리턴이 될 것이다. 리턴된 &lt;b&gt;libraries&lt;/b&gt;객체는 &lt;b&gt;Library&lt;/b&gt;타입 리졸버의 &lt;b&gt;parent&lt;/b&gt;에 담기게 되고, &lt;b&gt;books&lt;/b&gt;필드에서 참조할 수 있다. 똑같은 방식으로 &lt;b&gt;books&lt;/b&gt;에서 리턴한 객체는 &lt;b&gt;Book&lt;/b&gt;의 &lt;b&gt;author&lt;/b&gt;필드에서 &lt;b&gt;parent&lt;/b&gt;로 참조가 가능하다. 마지막으로 &lt;b&gt;author&lt;/b&gt;필드에서 &lt;b&gt;name&lt;/b&gt;프로퍼티를 가진 객체를 리턴하는 것을 볼 수 있는데, 이렇게 되면 위에서 설명한 &lt;b&gt;Author&lt;/b&gt;타입의 &lt;b&gt;name&lt;/b&gt;필드의 default resolver가 &lt;b&gt;parent&lt;/b&gt;객체의 &lt;b&gt;name&lt;/b&gt;프로퍼티 값을 리턴하게 된다. 그리고 String 형태인 Scalar 값이 리턴되었으므로 리졸버 체인이 종료된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;103&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MFC0A/btrfbmaMCSi/3WRIMazKLa3qh9GTqhTX10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MFC0A/btrfbmaMCSi/3WRIMazKLa3qh9GTqhTX10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MFC0A/btrfbmaMCSi/3WRIMazKLa3qh9GTqhTX10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMFC0A%2FbtrfbmaMCSi%2F3WRIMazKLa3qh9GTqhTX10%2Fimg.png&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;103&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.apollographql.com/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.apollographql.com/docs/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://graphql.org/learn/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://graphql.org/learn/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>GraphQL</category>
      <category>GraphQL</category>
      <category>query</category>
      <category>resolver</category>
      <category>리졸버</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/128</guid>
      <comments>https://gingerkang.tistory.com/128#entry128comment</comments>
      <pubDate>Thu, 16 Sep 2021 00:13:25 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] 드래그 앤 드롭 구현하기</title>
      <link>https://gingerkang.tistory.com/127</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ENytV/btrfbYNGWnr/9B8Ug3wAqU77h34EtpmmLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ENytV/btrfbYNGWnr/9B8Ug3wAqU77h34EtpmmLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ENytV/btrfbYNGWnr/9B8Ug3wAqU77h34EtpmmLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FENytV%2FbtrfbYNGWnr%2F9B8Ug3wAqU77h34EtpmmLK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이번 포스팅에서는 JavaScript를 사용해 아래와 같은 드래그 앤 드롭을 구현해 볼 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2gP1r/btrfbBefOc3/bDQCp6Mgura3TywCTkrwo1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2gP1r/btrfbBefOc3/bDQCp6Mgura3TywCTkrwo1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2gP1r/btrfbBefOc3/bDQCp6Mgura3TywCTkrwo1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b2gP1r/btrfbBefOc3/bDQCp6Mgura3TywCTkrwo1/img.gif&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;드래그 앤 드롭을 구현할 때는 &lt;b&gt;draggable&lt;/b&gt; 속성과 &lt;b&gt;dragstart&lt;/b&gt;, &lt;b&gt;dragend&lt;/b&gt; 이벤트를 사용한다. &lt;b&gt;draggable&lt;/b&gt; 속성은 Boolean 값이며 &lt;b&gt;true&lt;/b&gt;일 경우 요소를 드래그 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 기본 html과 css이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631698668902&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;button class=&quot;draggable&quot; draggable=&quot;true&quot;&amp;gt; &amp;lt;/button&amp;gt;
    &amp;lt;button class=&quot;draggable&quot; draggable=&quot;true&quot;&amp;gt; &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;button class=&quot;draggable&quot; draggable=&quot;true&quot;&amp;gt; &amp;lt;/button&amp;gt;
    &amp;lt;button class=&quot;draggable&quot; draggable=&quot;true&quot;&amp;gt; &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;script src=&quot;main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631698682726&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;body {
  margin: 0;
  padding: 0;
  background-color: #3d4e56fa;
  display: flex;
}

button {
  width: 70px;
  height: 70px;
  border: none;
  border-radius: 8px;
  margin: 12px;
  cursor: move;
  font-size: 30px;
  background: #eaeaea4f;
}

.container {
  margin: 10px;
  padding: 10px;
  background-color: #b5c1dc57;
  border-radius: 8px;
}

.draggable.dragging {
  opacity: 0.5;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;처음에는 두 개의 &lt;b&gt;container&lt;/b&gt;에 버튼이 각각 두 개씩 있다. 버튼의 개수는 물론, &lt;b&gt;container&lt;/b&gt;의 개수는 상관이 없다. 드래그 할 요소인 버튼이 어떤 &lt;b&gt;container&lt;/b&gt;에 들어가는지 계속해서 확인해 줄 것이기 때문에 &lt;b&gt;container&lt;/b&gt;는 1개로 해도 되고 10개로 해도 괜찮다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 기본적인 스타일을 해준다. &lt;b&gt;cursor&lt;/b&gt;에 &lt;b&gt;move&lt;/b&gt;라는 값을 주면 해당 요소가 드래그 될 수 있다고 표시를 해준다. 그리고 현재 드래그 하고 있는 요소를 나타내기 위해 &lt;b&gt;opacity&lt;/b&gt; 설정을 해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음은 이제 각 요소와 컨테이너에 이벤트 리스너를 달아줄 차례이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631699474553&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const draggables = document.querySelectorAll(&quot;.draggable&quot;);
const containers = document.querySelectorAll(&quot;.container&quot;);

draggables.forEach(draggable =&amp;gt; {
  draggable.addEventListener(&quot;dragstart&quot;, () =&amp;gt; {
    draggable.classList.add(&quot;dragging&quot;);
  });

  draggable.addEventListener(&quot;dragend&quot;, () =&amp;gt; {
    draggable.classList.remove(&quot;dragging&quot;);
  });
});

containers.forEach(container =&amp;gt; {
  container.addEventListener(&quot;dragover&quot;, e =&amp;gt; {
    e.preventDefault();
    const draggable = document.querySelector(&quot;.dragging&quot;);
    container.appendChild(draggable);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각 버튼에는 &lt;b&gt;dragstart&lt;/b&gt;와 &lt;b&gt;dragend&lt;/b&gt;를 달아주고, 드래그가 시작되면 해당 요소에 &lt;b&gt;dragging&lt;/b&gt;이라는 클래스명을 추가시키고 끝나면 삭제한다. 컨테이너에는 &lt;b&gt;dragover&lt;/b&gt;이벤트를 달아주고, 드래그하고 있는 요소가 해당 컨테이너 위에 있을 경우 자식으로 이어 붙인다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지 했으면 다음과 같이 동작할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;224&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzhGv5/btre5Oe9KeL/H7B9LKM6pxcuBQ0QxHKxWK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzhGv5/btre5Oe9KeL/H7B9LKM6pxcuBQ0QxHKxWK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzhGv5/btre5Oe9KeL/H7B9LKM6pxcuBQ0QxHKxWK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bzhGv5/btre5Oe9KeL/H7B9LKM6pxcuBQ0QxHKxWK/img.gif&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;224&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 구현해야 할 것이 드래그하는 요소가 다른 요소 사이에 들어가는 것이다. &lt;b&gt;getDragAfterElement&lt;/b&gt;라는 함수를 만들 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631700437739&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function getDragAfterElement(container, x) {
  const draggableElements = [
    ...container.querySelectorAll(&quot;.draggable:not(.dragging)&quot;),
  ];

  return draggableElements.reduce(
    (closest, child) =&amp;gt; {
      const box = child.getBoundingClientRect();
      const offset = x - box.left - box.width / 2;
      // console.log(offset);
      if (offset &amp;lt; 0 &amp;amp;&amp;amp; offset &amp;gt; closest.offset) {
        return { offset: offset, element: child };
      } else {
        return closest;
      }
    },
    { offset: Number.NEGATIVE_INFINITY },
  ).element;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다른 요소 사이에 들어가기 위해서는 다른 요소들과 현재 드래그 하고 있는 요소의 상대적 위치를 알아야 한다. 그게 바로 위에서 &lt;b&gt;offset&lt;/b&gt;이다. &lt;b&gt;draggableElements&lt;/b&gt;에 컨테이너에서 드래그 하고 있지 않은 요소들을 담는다. 그리고 그 요소들의 &lt;b&gt;offset&lt;/b&gt;를 통해 드래그 하고 있는 요소가 어떤 요소 앞에 들어가야 하는지 알려줄 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;box&lt;/b&gt;에는 &lt;b&gt;getBoundingClientRect&lt;/b&gt;값으로 각 요소의 위치 값이나, &lt;b&gt;height&lt;/b&gt;, &lt;b&gt;width&lt;/b&gt; 값들이 들어가있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zemGF/btre7t2oFcJ/XWHQJHEidqebimwFiPtINk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zemGF/btre7t2oFcJ/XWHQJHEidqebimwFiPtINk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zemGF/btre7t2oFcJ/XWHQJHEidqebimwFiPtINk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzemGF%2Fbtre7t2oFcJ%2FXWHQJHEidqebimwFiPtINk%2Fimg.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 &lt;b&gt;box&lt;/b&gt;의 &lt;b&gt;left&lt;/b&gt;와 &lt;b&gt;width&lt;/b&gt;, 드래그 하고 있는 요소의 X값을 비교하여 &lt;b&gt;offset&lt;/b&gt;값을 도출해낼 수 있다. 실제로 &lt;b&gt;offset&lt;/b&gt;값을 찍어보면 드래그를 하고 있는 요소가 &lt;b&gt;box&lt;/b&gt;의 가운데를 기준으로 왼쪽에 있으면 음수 값, 오른쪽에 있으면 양수 값을 가지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우리는 드래그를 통해 놓을 위치의 오른쪽 요소만 알아내면 된다. 즉, &lt;b&gt;offset&lt;/b&gt; 값이 음수를 갖게 되면 된다. 그리고 &lt;b&gt;closest&lt;/b&gt;를 통해 어디에 더 가까운지 알아낼 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;getDragAfterElement&lt;/b&gt;에서 반환된 &lt;b&gt;element&lt;/b&gt;는 컨테이너의 이벤트 리스너에서 받아와 남은 작업들을 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631701384417&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;containers.forEach(container =&amp;gt; {
  container.addEventListener(&quot;dragover&quot;, e =&amp;gt; {
    e.preventDefault();
    const afterElement = getDragAfterElement(container, e.clientX);
    console.log(afterElement)
    const draggable = document.querySelector(&quot;.dragging&quot;);
    container.appendChild(draggable);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;요소들을 드래그하면서 &lt;b&gt;afterElement&lt;/b&gt;를 보면 현재 위치에서 가장 가까운 오른쪽 요소를 나타내는 것을 알 수 있다. 드래그 하고 있는 요소가 가장 오른쪽에 있을 경우(오른쪽에 다른 요소가 없을 경우)에는 &lt;b&gt;element&lt;/b&gt; 프로퍼티가 없기 때문에 &lt;b&gt;undefined&lt;/b&gt;일 것이다. 따라서 다음과 같이 해줄 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631701641956&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;containers.forEach(container =&amp;gt; {
  container.addEventListener(&quot;dragover&quot;, e =&amp;gt; {
    e.preventDefault();
    const afterElement = getDragAfterElement(container, e.clientX);
    const draggable = document.querySelector(&quot;.dragging&quot;);
    if (afterElement === undefined) {
      container.appendChild(draggable);
    } else {
      container.insertBefore(draggable, afterElement);
    }
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;가장 오른쪽에 있을 경우(마지막 순서일 경우) &lt;b&gt;appendChild&lt;/b&gt;로 마지막에 이어 붙이고, 아닐 경우 &lt;b&gt;afterElement&lt;/b&gt;가 가리키는 요소 앞에 삽입한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;참고&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=jfYWwQrtzzY&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/watch?v=jfYWwQrtzzY&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JavaScript</category>
      <category>drag&amp;amp;drop</category>
      <category>javascript</category>
      <category>드래그앤드롭</category>
      <category>자바스크립트</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/127</guid>
      <comments>https://gingerkang.tistory.com/127#entry127comment</comments>
      <pubDate>Wed, 15 Sep 2021 19:35:39 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 코드를 더 깔끔하고 보기 좋게 작성하는 팁</title>
      <link>https://gingerkang.tistory.com/126</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zEuAw/btrbHbwuBqE/FIWNbaqRDgaWXF8AjzjRt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zEuAw/btrbHbwuBqE/FIWNbaqRDgaWXF8AjzjRt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zEuAw/btrbHbwuBqE/FIWNbaqRDgaWXF8AjzjRt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzEuAw%2FbtrbHbwuBqE%2FFIWNbaqRDgaWXF8AjzjRt1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;유튜브를 보다가 엘리님이 &lt;a href=&quot;https://www.youtube.com/watch?v=BUAhpB3FmS4&amp;amp;t=14s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;자바스크립트를 프로처럼 쓰는 팁&lt;/a&gt;에 관한 영상을 올리신걸 봤다. 마침 최근에 비슷한 주제의 &lt;a href=&quot;https://betterprogramming.pub/8-javascript-practices-to-make-your-code-look-and-work-better-45bff492f86a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;블로그 글&lt;/a&gt;을 본 적이 있어서 이참에 같이 정리를 하려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;i&gt;이 글에서 소개하는 팁들&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 배열 비우기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 한 번만 실행시키는 이벤트 리스너&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 'defer' 속성을 사용하자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 간결한 조건 체크(&amp;amp;&amp;amp;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5. 범위 내의 랜덤 숫자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6. 빈 객체 검사하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;7. 배열 내 중복 원소 제거&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;8. 객체 복사하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;9. 삼항 연산자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;10. Nullish coalescing 연산자(??)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;11. Default Parameter&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;12. Logical OR 연산자(||)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;13. Spread 연산자(...)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;14. Optional Chaining(?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;024&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/024.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/024.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 배열 비우기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;원소가 있는 배열을 빈 배열로 만들고자 할 때는&amp;nbsp;&lt;b&gt;length&lt;/b&gt;에 0을 주기만 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628533675621&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let foo = ['morinana', 'gintoki', 'kagura', 'hina']
foo.length = 0

console.log(foo) // []&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 한 번만 실행시키는 이벤트 리스너&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이벤트 리스너는 바닐라 JS 뿐만 아니라 React, Angular와 같은 프레임워크에서도 많이 사용된다. 버튼 클릭을 예로 들자. 보통은 버튼을 누를 때마다 특정 로직을 실행되게 이벤트 리스너를 단다. 근데 만약 딱 한 번만 실행되게 하고 싶다면, 가능할까? 옵션 하나로 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628531380685&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const button = document.querySelector('button');

button.addEventListener('click', () =&amp;gt; {
    console.log('한 번만 실행');
},{ once: true })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 'defer' 속성을 사용하자&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자바스크립트 코드는 HTML 파일에서 &lt;b&gt;script&lt;/b&gt;태그를 통해 다운로드되고 실행된다. 만약 &lt;b&gt;head&amp;nbsp;&lt;/b&gt;태그 안에 &lt;b&gt;script&lt;/b&gt;가 삽입되었다고 한다면, 어떻게 될까. 자바스크립트 파일이 간단하고 용량이 작으면 큰 차이를 못느끼겠지만 길고 엄청나게 클 경우에는 애플리케이션의 속도가 현저히 저하될 것이다. HTML 파일을 파싱할 때 &lt;b&gt;script&lt;/b&gt;태그를 만나면 HTML 파싱하는 것을 멈추고 &lt;b&gt;script&lt;/b&gt;를 실행시키기 때문이다. 그래서 보통 &lt;b&gt;body&lt;/b&gt; 태그 맨 밑에 &lt;b&gt;script&lt;/b&gt; 태그를 두곤한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 &lt;b&gt;defer&lt;/b&gt;속성을 사용하면 이럴 필요가 없다. 결론부터 말하자면 &lt;b&gt;defer&lt;/b&gt;속성을 통해 &lt;b&gt;script&lt;/b&gt;태그를 선언하면 &lt;b&gt;head&lt;/b&gt;태그 안에 작성하든, &lt;b&gt;body&lt;/b&gt;안에 작성하든, HTML를 파싱하면서 동시에 &lt;b&gt;script&lt;/b&gt;태그를 통해 자바스크립트 파일을 다운로드 시킬 수 있다. 그리고 HTML 파싱이 끝나면 다운로드된 자바스크립트를 실행시킨다. 즉 &lt;b&gt;script&lt;/b&gt;때문에 HTML 파싱이 멈추지 않게 되어 더이상 사용자가 빈 화면을 보지 않게 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628532128208&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;main.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 간결한 조건 체크(&amp;amp;&amp;amp;)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어떠한 조건을 체크할 때는 &lt;b&gt;if&lt;/b&gt; 대신 &lt;b&gt;&amp;amp;&amp;amp;&lt;/b&gt;연산자를 통해 좀 더 깔끔하게 작성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628532303836&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let condition = true

//  
if (condition) {
  foo()
}

//  
condition &amp;amp;&amp;amp; foo()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5. 범위 내의 랜덤 숫자&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;특정 범위 내에서 랜덤한 숫자를 간단하게 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628532426052&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function randomNumber(num1, num2) {
    return Math.random() * (num2 - num1) + num1
}

console.log(randomNumber(50, 100))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6. 빈 객체 검사하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;객체가 비어있는지 아닌지 검사할 때는 &lt;b&gt;Object.entries()&lt;/b&gt; 메서드를 통해 쉽게 확인이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628532802710&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let foo = {
  name: 'morinana',
  occupation: 'actor'
}
let bar = {}

console.log(Object.entries(foo).length === 0) // false
console.log(Object.entries(bar).length === 0) // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;7. 배열 내 중복된 원소 필터링&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;중복 원소를 제거할 때는 &lt;b&gt;Set&lt;/b&gt;을 사용하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628532942309&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let foo = ['morinana', 'gintoki', 'kagura', 'hina', 'gintoki', 'morinana']

console.log([...new Set(foo)]) // ['morinana', 'gintoki', 'kagura', 'hina']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;8. 객체 복사하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자바스크립트에서 객체는 참조 타입이기 때문에 &lt;b&gt;=&lt;/b&gt;로 객체를 복사할 경우 원하지 않은 결과를 보게될 것이다. 올바르게 객체를 복사하기 위해서는 아래와 같은 3가지 방법 중 하나를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628533434388&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let foo = {
  name: 'morinana',
  occupation: 'actress'
}

// Object.assign() 메서드
const cloneFoo1 = Object.assign({}, foo)

// JSON
const cloneFoo2 = JSON.parse(JSON.stringify(foo))

// Spread 연산자
const cloneFoo3 = { ...foo }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;9. 삼항 연산자&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어떤 로직에서 두 가지 조건에 의해 결과가 나눠진다면 삼항 연산자를 사용하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628534323932&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//  
function getResult(n) {
  let result
  if (n &amp;gt; 5) {
    result = ' '
  } else {
    result = ' '
  }
  
  return result
}

//  
function getResult(n) {
  return n &amp;gt; 5 ? ' ' : ' '
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;10. Nullish coalescing 연산자(??)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;null&lt;/b&gt;이거나 &lt;b&gt;undefined&lt;/b&gt;일 경우를 체크할 때는 &lt;b&gt;??&lt;/b&gt;연산자를 사용하자. &lt;b&gt;??&lt;/b&gt;연산자 기준 왼쪽 값이 &lt;b&gt;null&lt;/b&gt;이거나 &lt;b&gt;undefined&lt;/b&gt;일 경우 오른쪽 값을 채택한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628534944255&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//  
function printMsg(text) {
  let msg = text
  if (text === null || text === undefined) {
    msg = '❌'
  }
  console.log(msg)
}

//  
function printMsg(text) {
  const msg = text ?? '❌'
  console.log(msg)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;11. Default Parameter&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;함수 파라미터의 기본 값을 설정할 수 있다. 이때 이 Default Parameter는 &lt;b&gt;undefined&lt;/b&gt;일 때만 적용이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628535136723&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function printMsg(text = '❌') {
  console.log(text)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;12. Logical OR 연산자(||)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;||&lt;/b&gt;연산자 기준 왼쪽 값이 falsy일 경우 오른쪽 값을 채택한다. falsy에는 &lt;b&gt;null&lt;/b&gt;, &lt;b&gt;undefined&lt;/b&gt; 뿐만 아니라 0, -0, '', &lt;b&gt;false&lt;/b&gt; 등도 포함된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628535301821&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function printMsg(text) {
  const msg = text || '❌'
  console.log(msg)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;13. Spread 연산자(...)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;객체의 프로퍼티를 업데이트 하거나 기존 객체를 그대로 사용하면서 새로운 객체를 만들때는 Spread 연산자를 사용하자. 여러 개의 객체를 한 객체로 묶는 것도 가능하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628583867654&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const movie = { name: 'lastletter', year: '2020' }
const detail = { actress: 'morinana', genre: 'drama', director: 'iwaishunji' }

//  
movie['genre'] = detail.genre

//  
const newMovie = {
  name: movie.name,
  year: movie.year,
  actress: detail.actress,
  genre: detail.genre,
  director: detail.director
}

//  
const newMovie2 = Object.assign(movie, detail)
console.log(newMovie2)  // { name: 'lastletter', year: '2020', actress: 'morinana', genre: 'drama', director: 'iwaishunji' }

//  
const newMovie3 = { ...movie, ...detail, actress: 'hirosesuzu' }
console.log(newMovie2)  // { name: 'lastletter', year: '2020', actress: 'hirosesuzu', genre: 'drama', director: 'iwaishunji' }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;14. Optional chaining(?)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;특정 값이나 조건들의 값이 유효한지 여러개 나열해서 검사할 때는 Optional Chaining을 사용하자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628584587106&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const nana = {
  age: 19
}

const suzu = {
  age: 23,
  job: {
    title: 'actress'
  }
}

//  
function displayJobTitle(person) {
  if (person.job &amp;amp;&amp;amp; person.job.title) {
    console.log(person.job.title)
  }
}

//  
function displayJobTitle(person) {
  if (person.job?.title) {
    console.log(person.job.title)
  }
}

displayJobTitle(nana) // undefined
displayJobTitle(suzu) // actress&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;참고&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BUAhpB3FmS4&amp;amp;t=14s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/watch?v=BUAhpB3FmS4&amp;amp;t=14s&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://betterprogramming.pub/8-javascript-practices-to-make-your-code-look-and-work-better-45bff492f86a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://betterprogramming.pub/8-javascript-practices-to-make-your-code-look-and-work-better-45bff492f86a&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JavaScript</category>
      <category>자바스크립트</category>
      <category>클린코드</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/126</guid>
      <comments>https://gingerkang.tistory.com/126#entry126comment</comments>
      <pubDate>Tue, 10 Aug 2021 17:43:14 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 표 편집 / Python / 2021 카카오 채용연계형 인턴십</title>
      <link>https://gingerkang.tistory.com/125</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;564&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDwobP/btq98doNaC4/eMkLcU8DpApCtfhVPFmujK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDwobP/btq98doNaC4/eMkLcU8DpApCtfhVPFmujK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDwobP/btq98doNaC4/eMkLcU8DpApCtfhVPFmujK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDwobP%2Fbtq98doNaC4%2FeMkLcU8DpApCtfhVPFmujK%2Fimg.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;564&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;001&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/81303&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/81303&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1626796296599&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 표 편집&quot; data-og-description=&quot;8 2 [&amp;quot;D 2&amp;quot;,&amp;quot;C&amp;quot;,&amp;quot;U 3&amp;quot;,&amp;quot;C&amp;quot;,&amp;quot;D 4&amp;quot;,&amp;quot;C&amp;quot;,&amp;quot;U 2&amp;quot;,&amp;quot;Z&amp;quot;,&amp;quot;Z&amp;quot;] &amp;quot;OOOOXOOO&amp;quot; 8 2 [&amp;quot;D 2&amp;quot;,&amp;quot;C&amp;quot;,&amp;quot;U 3&amp;quot;,&amp;quot;C&amp;quot;,&amp;quot;D 4&amp;quot;,&amp;quot;C&amp;quot;,&amp;quot;U 2&amp;quot;,&amp;quot;Z&amp;quot;,&amp;quot;Z&amp;quot;,&amp;quot;U 1&amp;quot;,&amp;quot;C&amp;quot;] &amp;quot;OOXOXOOO&amp;quot;&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/81303&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/81303&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4XlF4/hyKWKx27rQ/oC1pBK4jmMgZDXKGgTEOr0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/Rlo7x/hyKWQkKcGC/nm1qIoR1wvLWxLJFx5v8FK/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/kjssw/hyKXZtzSya/5kKU1CDDqWYukLiFqJFKL1/img.png?width=520&amp;amp;height=400&amp;amp;face=0_0_520_400&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/81303&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/81303&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4XlF4/hyKWKx27rQ/oC1pBK4jmMgZDXKGgTEOr0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/Rlo7x/hyKWQkKcGC/nm1qIoR1wvLWxLJFx5v8FK/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/kjssw/hyKXZtzSya/5kKU1CDDqWYukLiFqJFKL1/img.png?width=520&amp;amp;height=400&amp;amp;face=0_0_520_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 표 편집&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;8 2 [&quot;D 2&quot;,&quot;C&quot;,&quot;U 3&quot;,&quot;C&quot;,&quot;D 4&quot;,&quot;C&quot;,&quot;U 2&quot;,&quot;Z&quot;,&quot;Z&quot;] &quot;OOOOXOOO&quot; 8 2 [&quot;D 2&quot;,&quot;C&quot;,&quot;U 3&quot;,&quot;C&quot;,&quot;D 4&quot;,&quot;C&quot;,&quot;U 2&quot;,&quot;Z&quot;,&quot;Z&quot;,&quot;U 1&quot;,&quot;C&quot;] &quot;OOXOXOOO&quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Solution&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1626796343382&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(n, k, cmds):
    nodes = {0: [n-1, 1]}
    for i in range(1, n):
        if i == n-1:
            nodes[i] = [i-1, 0]
        else:
            nodes[i] = [i-1, i+1]
            
    stack = []
    for cmd in cmds:
        if len(cmd) &amp;gt; 1:
            c, x = cmd.split(' ')
            cnt = 0
            # &quot;D X&quot;
            if c == 'D':
                while cnt &amp;lt; int(x):
                    k = nodes[k][1]
                    cnt += 1
            # &quot;U X&quot;
            else:
                while cnt &amp;lt; int(x):
                    k = nodes[k][0]
                    cnt += 1
        else:
            # &quot;C&quot;
            if cmd == 'C':
                nodes[nodes[k][0]][1] = nodes[k][1]
                nodes[nodes[k][1]][0] = nodes[k][0]
                stack.append([k, nodes[k]])
                tmp = nodes[k]
                del nodes[k]
                
                if tmp[1] == 0:
                    k = tmp[0]
                else:
                    k = tmp[1]
            # &quot;Z&quot;
            else:
                curr_node, val = stack.pop()
                prev_node, next_node = val
                nodes[curr_node] = [prev_node, next_node]
                nodes[prev_node][1] = curr_node
                nodes[next_node][0] = curr_node
    
    result = ''
    for i in range(n):
        if nodes.get(i) is None:
            result += 'X'
        else:
            result += 'O'
    
    return result&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 문제는 &lt;b&gt;n&lt;/b&gt;이 1,000,000에 &lt;b&gt;cmd&lt;/b&gt;가 200,000이므로 효율성까지 통과하려면 배열로는 어림도 없다. 그래서&amp;nbsp;처음 봤을 때 링크드리스트로 풀어야겠다는 생각을 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇게 처음에는 노드 클래스를 만들고 링크드리스트를 구현해가며 풀었다. 근데 이렇게 풀면 고려해야할게 한 두개가 아니였다. 각 행을 삭제하거나 삽입할 때 해당 노드와 이전 노드, 다음 노드를 다 기억해야하고 현재 가리키고 있는 노드와 &lt;b&gt;k&lt;/b&gt;값을 관리하기도 해야했다. 점점 코드가 길어지고 100줄이 넘어갔다. 뭐..맞기만하면 괜찮다고 생각했기때문에 제출했는데 아주 혼쭐이 났다. 여러 테스트케이스 만들어서 추가해봤는데 다 통과하기도 하고 문제를 못 찾겠어서 접근방식을 바꿔야겠다고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;바꾼게 위의 풀이이다. 링크드리스트를 &lt;b&gt;dict&lt;/b&gt;로 표현했다. 코드도 훨씬 간결하고 무엇보다 쉽게 구현할 수 있다. &lt;b&gt;dict&lt;/b&gt;에서 한 원소가 노드를 가리키고 &lt;b&gt;key&lt;/b&gt;는 행 번호를, &lt;b&gt;value&lt;/b&gt;는 이전 노드의 번호와 다음 노드의 번호를 배열 형태로 담았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626797277205&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nodes = {0: [n-1, 1]}
for i in range(1, n):
    if i == n-1:
        nodes[i] = [i-1, 0]
    else:
        nodes[i] = [i-1, i+1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이때, 첫 번째 노드의 이전 노드는 마지막 노드를 가리키고 마지막 노드의 다음 노드는 첫 번째 노드를 가리키는 원형 구조를 갖는 것에 주의한다. 아래와 같은 형태이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;173&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjIdyt/btq97aTBrlz/UGnQZ6y6IepEmK0JoYc691/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjIdyt/btq97aTBrlz/UGnQZ6y6IepEmK0JoYc691/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://aerocode.net/181&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjIdyt/btq97aTBrlz/UGnQZ6y6IepEmK0JoYc691/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjIdyt%2Fbtq97aTBrlz%2FUGnQZ6y6IepEmK0JoYc691%2Fimg.png&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;173&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;nbsp;https://aerocode.net/181&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 각 커맨드 별로 코드를 구현해보자. 먼저 D와 U이다. 이 둘은 현재 가리키고 있는 노드를 조작한다. 단순하게 k값을 가지고 숫자만 더해줬는데 이렇게 하면 &lt;b&gt;dict&lt;/b&gt;에서 없는 노드를 참조할 수 있기 때문에 잘못된 방법이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;dict&lt;/b&gt;로 구현한 링크드리스트에서는 이전 노드와 다음 노드의 번호를 알고 있기 때문에 그 번호를 타고타고 올라가거나 내려가면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626797735993&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if len(cmd) &amp;gt; 1:
    c, x = cmd.split(' ')
    cnt = 0
    if c == 'D':
        while cnt &amp;lt; int(x):
            k = nodes[k][1]
            cnt += 1
    else:
        while cnt &amp;lt; int(x):
            k = nodes[k][0]
            cnt += 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위처럼 &lt;b&gt;cnt&lt;/b&gt;변수를 하나 생성해서 현재 노드에서 &lt;b&gt;x&lt;/b&gt;번 만큼 이동하면 된다. D일 경우 다음 노드로 이동하면 되고 U일 경우 이전 노드로 이동하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예를 들어 예제에서 처음 &lt;b&gt;k&lt;/b&gt;가 2이므로 두 번째 노드를 가리키고 있을 것이다. 다음 커맨드가 &quot;D 2&quot;일 경우 두 번째 노드에서부터 2번만 다음 노드로 가면된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음 커맨드는 C이다. 해당 커맨드에서 핵심은 삭제이다. 링크드리스트에서 삭제 연산은 그렇게 어렵지 않다. 이전 노드의 next가 삭제될 노드의 다음 노드를 가리키게 하고, 다음 노드의 prev가 삭제될 노드의 이전 노드를 가리키게 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626798130371&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if cmd == 'C':
    nodes[nodes[k][0]][1] = nodes[k][1]
    nodes[nodes[k][1]][0] = nodes[k][0]
    stack.append([k, nodes[k]])
    tmp = nodes[k]
    del nodes[k]
    
    # 마지막 노드일 경우
    if tmp[1] == 0:
        k = tmp[0]
    else:
        k = tmp[1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;보기에는 좀 헷갈릴 수 있는데, &lt;b&gt;nodes[k][0]&lt;/b&gt;은 해당 노드의 이전 노드를 가리킨다. 즉 &lt;b&gt;nodes[nodes[k][0]][1]&lt;/b&gt;은 이전노드의 next라고 생각하면 된다. 여기에 &lt;b&gt;nodes[k][1]&lt;/b&gt; 즉, 삭제되는 노드가 가리키던 다음 노드를 가리키게 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;반대로 &lt;b&gt;nodes[nodes[k][1]][0] = nodes[k][0]&lt;/b&gt;은 다음 노드의 prev에 삭제되는 노드가 가리키던 이전 노드를 넣어주는 코드이다. 한번 이해하면 굉장히 쉬운 코드이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇게 삭제되는 노드의 행 번호 값과 이전 노드와 다음 노드 정보를 배열 형태로 &lt;b&gt;stack&lt;/b&gt;에 넣어주고 &lt;b&gt;del&lt;/b&gt; 연산자를 통해 링크드리스트에서 삭제해준다. &lt;b&gt;tmp&lt;/b&gt;변수에 삭제되는 노드를 담는 것을 볼 수 있는데, 현재 가리키는 노드인 &lt;b&gt;k&lt;/b&gt;를 관리해주기 위해서다. 마지막 노드가 삭제될 경우 삭제되는 노드의 이전 노드를 가리켜야 하므로 &lt;b&gt;k&lt;/b&gt;에 &lt;b&gt;tmp[0]&lt;/b&gt;을 담아주고 마지막 노드가 아닐 경우 삭제되는 노드의 다음 노드를 가리키게 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막 커맨드는 Z이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;stack&lt;/b&gt;에 있는 노드를 꺼내 원래 있던 자리에 넣어주기만 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626798626423&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curr_node, val = stack.pop()
prev_node, next_node = val
nodes[curr_node] = [prev_node, next_node]
nodes[prev_node][1] = curr_node
nodes[next_node][0] = curr_node&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;삽입할 때는 간단하다. 이전 노드의 next가 삽입되는 노드를 가리키게하고 다음 노드의 prev가 삽입되는 노드를 가리키게 하면 된다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;008&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/008.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/008.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/프로그래머스</category>
      <category>2021 카카오 채용연계형 인턴십</category>
      <category>카카오</category>
      <category>표 편집</category>
      <category>프로그래머스</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/125</guid>
      <comments>https://gingerkang.tistory.com/125#entry125comment</comments>
      <pubDate>Wed, 21 Jul 2021 01:33:18 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 거리두기 확인하기 / Python / 2021 카카오 채용연계형 인턴십</title>
      <link>https://gingerkang.tistory.com/124</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;564&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/plYok/btq97OW7P14/eHmwmOhFco3nv5IIMLwOu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/plYok/btq97OW7P14/eHmwmOhFco3nv5IIMLwOu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/plYok/btq97OW7P14/eHmwmOhFco3nv5IIMLwOu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FplYok%2Fbtq97OW7P14%2FeHmwmOhFco3nv5IIMLwOu0%2Fimg.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;564&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;001&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/81302&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/81302&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1626794822542&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 거리두기 확인하기&quot; data-og-description=&quot;[[&amp;quot;POOOP&amp;quot;, &amp;quot;OXXOX&amp;quot;, &amp;quot;OPXPX&amp;quot;, &amp;quot;OOXOX&amp;quot;, &amp;quot;POXXP&amp;quot;], [&amp;quot;POOPX&amp;quot;, &amp;quot;OXPXP&amp;quot;, &amp;quot;PXXXO&amp;quot;, &amp;quot;OXXXO&amp;quot;, &amp;quot;OOOPP&amp;quot;], [&amp;quot;PXOPX&amp;quot;, &amp;quot;OXOXP&amp;quot;, &amp;quot;OXPOX&amp;quot;, &amp;quot;OXXOP&amp;quot;, &amp;quot;PXPOX&amp;quot;], [&amp;quot;OOOXX&amp;quot;, &amp;quot;XOOOX&amp;quot;, &amp;quot;OOOXX&amp;quot;, &amp;quot;OXOOX&amp;quot;, &amp;quot;OOOOO&amp;quot;], [&amp;quot;PXPXP&amp;quot;, &amp;quot;XPXPX&amp;quot;, &amp;quot;PXPXP&amp;quot;, &amp;quot;XPXPX&amp;quot;, &amp;quot;PXPXP&amp;quot;]] [1, 0, 1, 1, 1]&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/81302&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/81302&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKvBKI/hyKX0eFap6/ucPksqO3ySuDAt0T2uE5U1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/dQj7fl/hyKWGB7orA/oAHq9RbX3EYKNLtd4kJQ61/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/0Jel4/hyKWItaL64/3inTS5kxpbf6dgNMoXyb9K/img.png?width=242&amp;amp;height=242&amp;amp;face=0_0_242_242&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/81302&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/81302&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKvBKI/hyKX0eFap6/ucPksqO3ySuDAt0T2uE5U1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/dQj7fl/hyKWGB7orA/oAHq9RbX3EYKNLtd4kJQ61/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/0Jel4/hyKWItaL64/3inTS5kxpbf6dgNMoXyb9K/img.png?width=242&amp;amp;height=242&amp;amp;face=0_0_242_242');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 거리두기 확인하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[[&quot;POOOP&quot;, &quot;OXXOX&quot;, &quot;OPXPX&quot;, &quot;OOXOX&quot;, &quot;POXXP&quot;], [&quot;POOPX&quot;, &quot;OXPXP&quot;, &quot;PXXXO&quot;, &quot;OXXXO&quot;, &quot;OOOPP&quot;], [&quot;PXOPX&quot;, &quot;OXOXP&quot;, &quot;OXPOX&quot;, &quot;OXXOP&quot;, &quot;PXPOX&quot;], [&quot;OOOXX&quot;, &quot;XOOOX&quot;, &quot;OOOXX&quot;, &quot;OXOOX&quot;, &quot;OOOOO&quot;], [&quot;PXPXP&quot;, &quot;XPXPX&quot;, &quot;PXPXP&quot;, &quot;XPXPX&quot;, &quot;PXPXP&quot;]] [1, 0, 1, 1, 1]&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Solution&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1626794855880&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

dx = [-1, 1, 0, 0]
dy = [0, 0, 1, -1]

def bfs(place, person):
    visited = [[False] * 5 for _ in range(5)]
    count = 0
    q = deque()
    q.append(person)
    while q:
        for _ in range(len(q)):
            y, x = q.popleft()
            visited[y][x] = True
            for i in range(4):
                nx = x + dx[i]
                ny = y + dy[i]
                if nx &amp;lt; 0 or ny &amp;lt; 0 or nx &amp;gt;= 5 or ny &amp;gt;= 5 or visited[ny][nx]:
                    continue
                if place[ny][nx] == 'P':
                    return False
                elif place[ny][nx] == 'X':
                    continue
                else:
                    q.append((ny, nx))
                    
        count += 1
        if count == 2 or not q:
            return True

def solution(places):
    ans = []
    for place in places:
        people = deque()
        for i in range(5):
            for j in range(5):
                if place[i][j] == 'P':
                    people.append((i, j))
                    
        if len(people) == 0:
            ans.append(1)
            continue
        
        flag = True
        for person in people:
            if not bfs(place, person):
                flag = False
                break
        
        if not flag:
            ans.append(0)
        else:
            ans.append(1)
        
    return ans&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;대기실은 5개이며, 각 대기실은 5x5 크기입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;거리두기를 위하여 응시자들 끼리는 맨해튼 거리가 2 이하로 앉지 말아 주세요.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;단 응시자가 앉아있는 자리 사이가 파티션으로 막혀 있을 경우에는 허용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2, 3번이 거리두기 규칙이다. 맨해튼 거리가 2 이하로 앉아야 하지만 파티션으로 막혀 있을 경우는 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, P O P는 규칙을 어긴 것이고, P X P는 규칙을 준수한 것이다. 그럼 이 규칙을 어떻게 확인할까.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;bfs를 통해 간단하게 확인할 수 있다. 나는 먼저 한 시험장에 있는 사람들을 &lt;b&gt;people&lt;/b&gt;에 담고 각각의 사람이 거리두기를 지키고 있는지 검사했다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;한 사람을 검사할 때 해당 위치로 부터 맨해튼 거리 2 이하인 곳을 확인한다. 처음 위치에서 상하좌우로 한번씩 검사하고, 파티션이 있을 경우를 제외하고 빈 자리이면 큐에 넣어준다. 당연히 응시자가 있을 경우 즉시 &lt;b&gt;False&lt;/b&gt;를 리턴한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇게 처음 상하좌우를 돌면 큐에 검사를 더 진행할 방향의 좌표들이 담겨있을 것이다. 여기서 이제 각 좌표마다 한번만 더 확인해주면 된다. 즉 두 번을 검사하면 되므로 &lt;b&gt;count&lt;/b&gt;라는 변수를 생성해 관리한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;count&lt;/b&gt;가 2가 되거나 큐에 좌표들이 담기지 않았으면 해당 사람 주위의 맨해튼 거리 2 이하로는 다 거리두기를 지키고 있다는 말이므로 &lt;b&gt;True&lt;/b&gt;를 리턴한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626796174504&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while q:
  for _ in range(len(q)):
      y, x = q.popleft()
      visited[y][x] = True
      for i in range(4):
          nx = x + dx[i]
          ny = y + dy[i]
          if nx &amp;lt; 0 or ny &amp;lt; 0 or nx &amp;gt;= 5 or ny &amp;gt;= 5 or visited[ny][nx]:
              continue
          if place[ny][nx] == 'P':
              return False
          elif place[ny][nx] == 'X':
              continue
          else:
              q.append((ny, nx))
                    
  count += 1
  if count == 2 or not q:
      return True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;008&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/008.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/008.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/프로그래머스</category>
      <category>2021 카카오 채용연계형 인턴</category>
      <category>거리두기 확인하기</category>
      <category>카카오</category>
      <category>프로그래머스</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/124</guid>
      <comments>https://gingerkang.tistory.com/124#entry124comment</comments>
      <pubDate>Wed, 21 Jul 2021 00:50:33 +0900</pubDate>
    </item>
    <item>
      <title>[React Query] Next.js + React Query로 무한 스크롤과 SSR 구현하기</title>
      <link>https://gingerkang.tistory.com/123</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;629&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx5Fts/btq7U06rlHj/lGYFLIx9ajKEWnKx09kAeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx5Fts/btq7U06rlHj/lGYFLIx9ajKEWnKx09kAeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx5Fts/btq7U06rlHj/lGYFLIx9ajKEWnKx09kAeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx5Fts%2Fbtq7U06rlHj%2FlGYFLIx9ajKEWnKx09kAeK%2Fimg.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;629&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;001&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;React Query?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/&quot;&gt;React Query&lt;/a&gt;란 간단하게 말하자면 데이터 패칭 라이브러리이다. 하지만 데이터를 패칭하는 기능만 지원하는게 아니라 Devtools, 캐싱, 서버 상태 동기화 및 업데이트 등 많은 기능을 지원한다. 사용은 &lt;a href=&quot;https://www.apollographql.com/docs/react/&quot;&gt;Apollo&lt;/a&gt;와 비슷하다. 똑같이 데이터가 필요한 곳에서 &lt;b&gt;useQuery&lt;/b&gt;를 사용하며 아까 언급했듯이 서버 데이터를 캐싱한다. 또한 &lt;i&gt;A&lt;/i&gt; 라는 데이터를 사용하는 컴포넌트가 여러개일 경우 각 컴포넌트들에서 같은 쿼리 키를 가진 &lt;b&gt;useQuery&lt;/b&gt;를 사용함으로써 한 번의 데이터 패칭으로 모두 &lt;i&gt;A&lt;/i&gt; 데이터를 사용할 수 있게 된다. 패칭하여 데이터를 스토어에 저장한 후 전역에서 해당 데이터를 사용할 수 있게 해주는 redux와 비슷한 것을 알 수 있으며 redux의 액션, 리듀서 등의 보일러플레이트를 작성하지 않고 서버 데이터를 관리할 수 있는 또 다른 대안이 될 것이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://github.com/tannerlinsley/react-query&quot;&gt;Github&lt;/a&gt; 스타도 오늘 기준 20.8k로 많은 인기를 끌고 있으며 개발이 활발하게 이루어지고 있다. &lt;a href=&quot;https://react-query.tanstack.com/overview&quot;&gt;공식 문서&lt;/a&gt;도 정리가 정말 잘 되어 있으니 관심이 있다면 한번 사용해보는 걸 추천한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;SSR(Server Side Rendering)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;React Query는 SSR을 두 가지 방식으로 구현할 수 있다. &lt;b&gt;initialData&lt;/b&gt;를 주는 방법과 &lt;b&gt;Hydration&lt;/b&gt;을 하는 방법이다. 여기서는 &lt;b&gt;Hydration&lt;/b&gt;을 통해 SSR을 구현한다. &lt;b&gt;initialData&lt;/b&gt;를 통한 방법이 데이터를 명시해주기만 하면 되기 때문에 훨씬 간단하긴 하지만 문제가 있다. 만약 여러 컴포넌트에서 해당 데이터를 SSR을 통해 사용자에게 보여준다고 한다면, 모든 컴포넌트에 &lt;b&gt;initialData&lt;/b&gt;를 넘겨줘야 하기 때문에 넘겨줘야 하는 대상 컴포넌트가 컴포넌트 트리 상에서 depth가 깊이 있는 컴포넌트일 때는 관리도 그렇고 비효율적이다. 반면 &lt;b&gt;Hydration&lt;/b&gt;을 통한 방법은 SSR을 할 때 원하는 쿼리를 prefetch하고 해당 쿼리를 사용하는 컴포넌트에서는 동일한 키로 &lt;b&gt;useQuery&lt;/b&gt; 훅을 호출하기만 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 프로젝트에서는 &lt;a href=&quot;https://pokeapi.co/&quot;&gt;PokeApi&lt;/a&gt;를 사용해서 간단하게 포켓몬들의 이름을 나열해보도록 하겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, Next.js 프로젝트의 &lt;b&gt;_app&lt;/b&gt;에서 &lt;b&gt;queryClient&lt;/b&gt;를 내려준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;// _app/index.tsx

import React from 'react'
import { QueryClient, QueryClientProvider } from 'react-query'
import { NextComponentType } from &quot;next&quot;
import { AppContext, AppInitialProps, AppProps } from &quot;next/app&quot;
import { Hydrate } from 'react-query/hydration'
import { ReactQueryDevtools } from 'react-query/devtools'

const MyApp: NextComponentType&amp;lt;AppContext, AppInitialProps, AppProps&amp;gt; = ({ Component, pageProps }) =&amp;gt; {
  const queryClientRef = React.useRef&amp;lt;QueryClient&amp;gt;()
  if (!queryClientRef.current) {
    queryClientRef.current = new QueryClient()
  }

  return &amp;lt;&amp;gt;
    &amp;lt;QueryClientProvider client={queryClientRef.current}&amp;gt;
      &amp;lt;Hydrate state={pageProps.dehydratedState}&amp;gt;
        &amp;lt;Component {...pageProps} /&amp;gt;
      &amp;lt;/Hydrate&amp;gt;
      &amp;lt;ReactQueryDevtools /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  &amp;lt;/&amp;gt;
}

MyApp.getInitialProps = async ({ Component, ctx }: AppContext): Promise&amp;lt;AppInitialProps&amp;gt; =&amp;gt; {
  let pageProps = {}

  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx)
  }

  return { pageProps }
}

export default MyApp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;dehydratedState&lt;/b&gt;는 &lt;b&gt;/poke&lt;/b&gt; 경로의 &lt;b&gt;getServerSideProps&lt;/b&gt;에서 반환해준 값을 사용할 것이다. &lt;b&gt;pages&lt;/b&gt;폴더에 &lt;b&gt;poke&lt;/b&gt;파일을 만들어주자. 여기서 무한스크롤에 사용되는 쿼리를 &lt;b&gt;prefetch&lt;/b&gt;하고, 그것을 &lt;b&gt;dehydrate&lt;/b&gt;하여 넘겨줄 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// pages/poke.tsx

import Pokemon from '../components/Pokemon'
import { getPoke } from '../api'
import { QueryClient } from 'react-query'
import { dehydrate } from 'react-query/hydration'

const Poke = () =&amp;gt; {
  return (
    &amp;lt;Pokemon /&amp;gt;
  )
}

export async function getServerSideProps() {
  const queryClient = new QueryClient()

  await queryClient.prefetchInfiniteQuery('poke', () =&amp;gt; getPoke(), { staleTime: 1000 })

  return {
    props: {
      dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))),
    }
  }
}

export default Poke &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient)))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위와 같이 dehydrate한 것을 &lt;b&gt;stringify&lt;/b&gt;했다가 다시 &lt;b&gt;parse&lt;/b&gt;하는 것은 &lt;b&gt;InfiniteQuery&lt;/b&gt;를 사용할 때 발생하는 이슈이다. 그냥 dehydrate한 상태로 넘겨주면 다음과 같은 오류가 난다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;131&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX1aLm/btq7UrDxrqa/CM4U7Dv06dZTuj2WhJr0Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX1aLm/btq7UrDxrqa/CM4U7Dv06dZTuj2WhJr0Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX1aLm/btq7UrDxrqa/CM4U7Dv06dZTuj2WhJr0Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX1aLm%2Fbtq7UrDxrqa%2FCM4U7Dv06dZTuj2WhJr0Ok%2Fimg.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;131&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;InfiniteQuery&lt;/b&gt;에서 맨 처음 페이지에 해당하는 데이터의 &lt;b&gt;pageParam&lt;/b&gt;이 &lt;b&gt;undefined&lt;/b&gt;로 설정되기 때문에 hydration과정에서 직렬화가 되지 않아서 저렇게 해주었다. 더 궁금하다면 &lt;a href=&quot;https://github.com/tannerlinsley/react-query/issues/1458&quot;&gt;이슈&lt;/a&gt;를 참고해보면 좋을 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지 했다면 &lt;b&gt;getServerSideProps&lt;/b&gt;에서 &lt;b&gt;props&lt;/b&gt;로 넘겨준 &lt;b&gt;dehydratedState&lt;/b&gt;를 &lt;b&gt;_app&lt;/b&gt;에서 받아 Hydration로 내려줄 것이다. 이제 컴포넌트에서 prefetch에서 사용된 쿼리와 같은 키인 &lt;b&gt;'poke'&lt;/b&gt; 사용하여 &lt;b&gt;useInfiniteQuery&lt;/b&gt; 훅을 호출하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 api를 통해 포켓몬 데이터를 받아오는 함수를 만들어주자. 루트 디렉토리에서 &lt;b&gt;api&lt;/b&gt;폴더를 하나 만들고 그 안에 &lt;b&gt;index.ts&lt;/b&gt;파일을 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// api/index.ts

import axios from 'axios'

export const getPoke = async (offset: number = 0) =&amp;gt; {
  const { data } = await axios.get(
    `https://pokeapi.co/api/v2/pokemon?limit=20&amp;amp;offset=${offset}`
  );
  return data;
} &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;무한 스크롤&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;components&lt;/b&gt;폴더에 &lt;b&gt;Pokemon.tsx&lt;/b&gt;파일을 만들어주자. 위에서도 언급했지만 React Query에서 무한 스크롤을 구현할 때는 주로 &lt;b&gt;useInfiniteQuery&lt;/b&gt;를 사용한다. 첫 번째 인자로 쿼리 키를 주고, 두 번째는 쿼리 함수, 마지막으로 쿼리에 적용할 옵션들을 넘겨준다. 여기서는 쿼리 키는 &lt;b&gt;'poke'&lt;/b&gt;이고 쿼리 함수는 위에서 만든 &lt;b&gt;getPoke&lt;/b&gt;함수가 되겠다. 그리고 &lt;b&gt;getNextPageParam&lt;/b&gt;라는 옵션에서 이전 페이지의 데이터를 통해 무한 스크롤을 언제 끝낼지 결정할 수 있다. 어떠한 값을 리턴할 경우 &lt;b&gt;useInfiniteQuery&lt;/b&gt;가 리턴하는 &lt;b&gt;result&lt;/b&gt; 객체의 &lt;b&gt;hasNextPage&lt;/b&gt;가 &lt;b&gt;true&lt;/b&gt;값이 되고, &lt;b&gt;undefined&lt;/b&gt;를 리턴할 경우 &lt;b&gt;false&lt;/b&gt;가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// components/Pokemon.tsx

import React, { useEffect, useState } from 'react'
import { useInfiniteQuery, useQuery } from 'react-query'
import { getPoke } from '../api'

const Pokemon = () =&amp;gt; {
  const { data, fetchNextPage } = useInfiniteQuery('poke', 
    ({ pageParam = '' }) =&amp;gt; getPoke(pageParam), 
    {
      getNextPageParam: (lastPage) =&amp;gt; {
        const lastOffset = lastPage.results[lastPage.results.length - 1].url.split('/')[6]
        if (lastOffset &amp;gt; 1118) {
          return undefined
        }
        return lastOffset
      },
      staleTime: 1000,
    }
  )

  return (
    &amp;lt;&amp;gt;
      &amp;lt;ul&amp;gt;
        {data.pages.map((page) =&amp;gt; (
          page.results.map((poke) =&amp;gt; (
            &amp;lt;li key={poke.name} style={{ padding: '20px', fontWeight: 'bold'}}&amp;gt;{poke.name}&amp;lt;/li&amp;gt;
          ))
        ))}
      &amp;lt;/ul&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; fetchNextPage()}&amp;gt;Load More&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위와 같이 &lt;b&gt;getNextPageParam&lt;/b&gt;에서 &lt;b&gt;lastPage&lt;/b&gt;를 인자로 받아와 다음 페이지를 로드할지 결정한다. &lt;b&gt;staleTime&lt;/b&gt;은 React Query가 관리하는 캐시에서 해당 쿼리를 어느 시점에 fresh상태에서 stale상태로 바꿀지 결정하는 옵션이다. 이에 대한 개념은 공식 문서에 자세히 나와있다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지만 해도 잘 동작한다. 하지만 &lt;b&gt;Load More&lt;/b&gt; 버튼을 눌러야 &lt;b&gt;fetchNextPage&lt;/b&gt; 함수가 실행되어 다음 페이지를 불러온다. &lt;b&gt;fetchNextPage&lt;/b&gt;에 대한 내용은 이따가 좀 더 자세히 다루겠다. 어쨋든 이건 무한 스크롤이라고 볼 수 없다. 이제 &lt;b&gt;IntersectionObserver&lt;/b&gt;를 통해 무한 스크롤을 구현해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서는 공식 문서대로 &lt;b&gt;useIntersectionObserver&lt;/b&gt; 훅을 만들어 사용할 것이다. &lt;b&gt;hooks&lt;/b&gt;폴더를 하나 만들고 &lt;b&gt;useIntersectionObserver.ts&lt;/b&gt;파일을 만들어주자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// hooks/useIntersectionObserver.ts

import React from 'react'

export default function useIntersectionObserver({
  root,
  target,
  onIntersect,
  threshold = 1.0,
  rootMargin = '0px',
  enabled = true,
}) {
  React.useEffect(() =&amp;gt; {
    if (!enabled) {
      return
    }

    const observer = new IntersectionObserver(
      entries =&amp;gt;
        entries.forEach(entry =&amp;gt; entry.isIntersecting &amp;amp;&amp;amp; onIntersect()),
      {
        root: root &amp;amp;&amp;amp; root.current,
        rootMargin,
        threshold,
      }
    )

    const el = target &amp;amp;&amp;amp; target.current

    if (!el) {
      return
    }

    observer.observe(el)

    return () =&amp;gt; {
      observer.unobserve(el)
    }
  }, [target, enabled, root, threshold, rootMargin, onIntersect])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;Pokemon&lt;/b&gt; 컴포넌트에서 해당 훅을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// components/Pokemon.tsx

import React, { useEffect, useState } from 'react'
import { useInfiniteQuery, useQuery } from 'react-query'
import { getPoke } from '../api'
import useIntersectionObserver from '../hooks/useIntersectionObserver'

const Pokemon = () =&amp;gt; {
  const { data, hasNextPage, fetchNextPage } = useInfiniteQuery('poke', 
    ({ pageParam = '' }) =&amp;gt; getPoke(pageParam), 
    {
      getNextPageParam: (lastPage) =&amp;gt; {
        const lastOffset = lastPage.results[lastPage.results.length - 1].url.split('/')[6]
        if (lastOffset &amp;gt; 1118) {
          return undefined
        }
        return lastOffset
      },
      staleTime: 3000,
    }
  )

  const loadMoreButtonRef = React.useRef()

  useIntersectionObserver({
    root: null,
    target: loadMoreButtonRef,
    onIntersect: fetchNextPage,
    enabled: hasNextPage,
  })

  return (
    &amp;lt;&amp;gt;
      &amp;lt;ul&amp;gt;
        {data.pages.map((page) =&amp;gt; (
          page.results.map((poke) =&amp;gt; (
            &amp;lt;li key={poke.name} style={{ padding: '20px', fontWeight: 'bold'}}&amp;gt;{poke.name}&amp;lt;/li&amp;gt;
          ))
        ))}
      &amp;lt;/ul&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; fetchNextPage()}&amp;gt;Load More&amp;lt;/button&amp;gt;
      &amp;lt;div ref={loadMoreButtonRef}/&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 하면 SSR과 무한 스크롤까지 잘 동작할 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;699&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwaDkU/btq7V7xhcpc/NEmgnMRZKoKwK0TKicWfL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwaDkU/btq7V7xhcpc/NEmgnMRZKoKwK0TKicWfL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwaDkU/btq7V7xhcpc/NEmgnMRZKoKwK0TKicWfL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwaDkU%2Fbtq7V7xhcpc%2FNEmgnMRZKoKwK0TKicWfL0%2Fimg.png&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;699&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;옆의 개발자 도구에서 네트워크 창을 보면 데이터 요청없이 SSR로 포켓몬 이름을 잘 출력하는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 스크롤을 좀 내리면 다음과 같은 결과가 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;699&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsAGg3/btq7U1YIlup/VzSWkFs66KyTC58Wx9vUqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsAGg3/btq7U1YIlup/VzSWkFs66KyTC58Wx9vUqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsAGg3/btq7U1YIlup/VzSWkFs66KyTC58Wx9vUqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsAGg3%2Fbtq7U1YIlup%2FVzSWkFs66KyTC58Wx9vUqk%2Fimg.png&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;699&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;네트워크 요청과 함께 다음 페이지의 데이터를 가져온다. &lt;b&gt;useInfiniteQuery&lt;/b&gt; 훅을 사용하여 간단하게 무한 스크롤을 구현했다. &lt;b&gt;fetchNextPage&lt;/b&gt;를 실행시키면 &lt;b&gt;getNextPageParam&lt;/b&gt;에서 다음 &lt;b&gt;pageParam&lt;/b&gt;을 받아서 쿼리 함수에 넘겨주게 된다. 여기서는 SSR을 하고, 첫 스크롤일 경우 &lt;b&gt;pageParam&lt;/b&gt;은 20이 되고 &lt;b&gt;getPoke&lt;/b&gt; 함수에 20이 넘어가게 된다. 그렇게 받아온 데이터는 &lt;b&gt;useInfiniteQuery&lt;/b&gt; 훅이 리턴하는 &lt;b&gt;result&lt;/b&gt; 객체의 &lt;b&gt;data&lt;/b&gt;에 차곡차곡 쌓이게 된다. 이 데이터는 위 사진에서 왼쪽 하단에 보이는 Devtools를 통해서도 확인이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1147&quot; data-origin-height=&quot;855&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bslOPg/btq7RJLLrjE/S5G7HbU8NKmEejUO0Nev00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bslOPg/btq7RJLLrjE/S5G7HbU8NKmEejUO0Nev00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bslOPg/btq7RJLLrjE/S5G7HbU8NKmEejUO0Nev00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbslOPg%2Fbtq7RJLLrjE%2FS5G7HbU8NKmEejUO0Nev00%2Fimg.png&quot; data-origin-width=&quot;1147&quot; data-origin-height=&quot;855&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;002&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/002.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/002.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;참고&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://react-query.tanstack.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://react-query.tanstack.com/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://tkdodo.eu/blog/practical-react-query#the-defaults-explained&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tkdodo.eu/blog/practical-react-query#the-defaults-explained&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>ReactQuery</category>
      <category>next.js</category>
      <category>React Query</category>
      <category>SSR</category>
      <category>리액트 쿼리</category>
      <category>무한 스크롤</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/123</guid>
      <comments>https://gingerkang.tistory.com/123#entry123comment</comments>
      <pubDate>Tue, 22 Jun 2021 21:03:06 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Dynamic Routes</title>
      <link>https://gingerkang.tistory.com/122</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;426&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blWCaW/btq2hYNw3Qk/wes9w3xal29Pljzy24MTy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blWCaW/btq2hYNw3Qk/wes9w3xal29Pljzy24MTy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blWCaW/btq2hYNw3Qk/wes9w3xal29Pljzy24MTy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblWCaW%2Fbtq2hYNw3Qk%2Fwes9w3xal29Pljzy24MTy1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;426&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;i&gt;이 글은&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;의 공식 튜토리얼 문서를 참고하여 작성한 글입니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;인덱스 페이지를 만들었지만 아직 블로그의 개인 포스트 페이지는 만들지 않았다. 이제 dynamic routes를 통해 각 포스트 페이지마다 고유의 URL을 갖게 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;외부 데이터에 의존하는 페이지 경로&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://gingerkang.tistory.com/121&quot;&gt;이전 글&lt;/a&gt;에서 &lt;b&gt;getStaticProps&lt;/b&gt;를 사용하여 인덱스 페이지를 만들면서 &lt;i&gt;블로그 콘텐&lt;/i&gt;츠가 외부 데이터에 의존하는 경우를 다루었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 글에서는 각 &lt;i&gt;페이지 경로&lt;/i&gt;가 외부 데이터에 의존하는 경우에 대해 다룰 것이다. Next.js는 외부 데이터에 의존하는 페이지를 경로와 함께 정적으로 생성하게 해준다. 이를 가능하게 하는 것이 dynamic URLs이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;1050&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G8AnE/btq2gR2EKgG/Uq2oYuCMVjsTwTwiPVvn51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G8AnE/btq2gR2EKgG/Uq2oYuCMVjsTwTwiPVvn51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G8AnE/btq2gR2EKgG/Uq2oYuCMVjsTwTwiPVvn51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG8AnE%2Fbtq2gR2EKgG%2FUq2oYuCMVjsTwTwiPVvn51%2Fimg.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;1050&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇다면 어떻게 페이지를 Dynamic Routes를 사용하여 정적 생성할까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;블로그에서 각 포스트의 경로가 &lt;b&gt;/posts/&amp;lt;id&amp;gt;&lt;/b&gt;로 지정되길 원한다고 하면, &lt;b&gt;&amp;lt;id&amp;gt;&lt;/b&gt;는 &lt;b&gt;posts&lt;/b&gt;디렉토리 안에 있는 마크다운 파일명이 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;예를 들어 이전 글에서 &lt;b&gt;ssg-ssr.md&lt;/b&gt;와 &lt;b&gt;pre-rendering.md&lt;/b&gt;를 만들어 놨으므로, 해당 글의 경로는 각각 &lt;b&gt;/posts/ssg-ssr.md&lt;/b&gt;와 &lt;b&gt;/posts/pre-rendering&lt;/b&gt;이 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getStaticPaths&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 &lt;b&gt;pages/posts&lt;/b&gt;안에 &lt;b&gt;[id].js&lt;/b&gt;파일을 만든다. Next.js에서 dynamic routes를 사용하는 페이지는 &lt;b&gt;[]&lt;/b&gt;로 감싸져있어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;first-post.js&lt;/b&gt;파일은 더이상 사용하지 않기 때문에 삭제해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages/posts/[id].js&lt;/b&gt;파일을 열어 일단 다음과 같이 작성해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618054901324&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Layout from &quot;../../components/layout&quot;;

export default function Post() {
  return &amp;lt;Layout&amp;gt;...&amp;lt;/Layout&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로 &lt;b&gt;lib/posts.js&lt;/b&gt;에서 &lt;b&gt;getAllPostIds&lt;/b&gt;함수를 밑에 추가 해준다. 이 함수는 &lt;b&gt;posts&lt;/b&gt;디렉토리 안에 있는 파일명 리스트를 리턴할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618055014399&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory);

  // 아래와 같은 형태로 파일명 리스트를 리턴한다.
  // [
  //   {
  //     params: {
  //       id: 'ssg-ssr'
  //     }
  //   },
  //   {
  //     params: {
  //       id: 'pre-rendering'
  //     }
  //   }
  // ]
  return fileNames.map(fileName =&amp;gt; {
    return {
      params: {
        id: fileName.replace(/\.md$/, &quot;&quot;),
      },
    };
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 코드에서 볼 수 있듯이 리턴되는 리스트는 문자열 형태가 아니라 객체 형태여야 한다. 각 객체는 &lt;b&gt;params&lt;/b&gt;라는 키와 &lt;b&gt;id&lt;/b&gt;키를 포함하고 있다. 그렇지 않으면 &lt;b&gt;getStaticPaths&lt;/b&gt;함수는 정상적으로 동작하지 않을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 &lt;b&gt;getAllPostIds&lt;/b&gt;함수를 import하고 &lt;b&gt;getStaticPaths&lt;/b&gt;함수 내부에서 사용한다. &lt;b&gt;pages/posts/[id].js&lt;/b&gt;에서 &lt;b&gt;Post&lt;/b&gt;컴포넌트 위에 다음과 같이 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618055225011&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { getAllPostIds } from &quot;../../lib/posts&quot;;

export async function getStaticPaths() {
  const paths = getAllPostIds();
  return {
    paths,
    fallback: false,
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이렇게 하면 &lt;b&gt;paths&lt;/b&gt;에는 &lt;b&gt;pages/posts/[id].js&lt;/b&gt;에서 정의한 params를 포함하는 &lt;b&gt;getAllPostIds&lt;/b&gt;에서 리턴한 배열이 담겨 있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getStaticProps&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 &lt;b&gt;id&lt;/b&gt;를 통해 포스트에 필요한 데이터를 가져올 필요가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이를 위해서 &lt;b&gt;lib/posts.js&lt;/b&gt;에 &lt;b&gt;getPostData&lt;/b&gt;함수를 추가해주자. 이 함수는 &lt;b&gt;id&lt;/b&gt;에 맞는 포스트 데이터를 리턴할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618055574236&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, &quot;utf8&quot;);

  // 포스트의 메타데이터를 파싱하기 위해 gray-matter 사용
  const matterResult = matter(fileContents);

  // 데이터를 id 와 병합시킨다.
  return {
    id,
    ...matterResult.data,
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;pages/posts/[id].js&lt;/b&gt;를 다음과 같이 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618055633367&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { getAllPostIds, getPostData } from &quot;../../lib/posts&quot;;

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 포스트 페이지는 &lt;b&gt;getStaticProps&lt;/b&gt;안에 있는 &lt;b&gt;getPostData&lt;/b&gt;를 통해 데이터를 받아올 수 있고 &lt;b&gt;props&lt;/b&gt;로 리턴할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618055746287&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// [id].js

export default function Post({ postData }) {
  return (
    &amp;lt;Layout&amp;gt;
      {postData.title}
      &amp;lt;br /&amp;gt;
      {postData.id}
      &amp;lt;br /&amp;gt;
      {postData.date}
    &amp;lt;/Layout&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 서버를 실행시켜 확인해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;824&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t0nVe/btq2iViulSi/JQ2ui6iWjCw38pkOxuop81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t0nVe/btq2iViulSi/JQ2ui6iWjCw38pkOxuop81/img.png&quot; data-alt=&quot;http://localhost:3000/posts/ssg-ssr&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t0nVe/btq2iViulSi/JQ2ui6iWjCw38pkOxuop81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft0nVe%2Fbtq2iViulSi%2FJQ2ui6iWjCw38pkOxuop81%2Fimg.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;824&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://localhost:3000/posts/ssg-ssr&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;824&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pYuyq/btq2hDW719C/kNbadahVUYwzdcIoK3JkY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pYuyq/btq2hDW719C/kNbadahVUYwzdcIoK3JkY0/img.png&quot; data-alt=&quot;http://localhost:3000/posts/pre-rendering&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pYuyq/btq2hDW719C/kNbadahVUYwzdcIoK3JkY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpYuyq%2Fbtq2hDW719C%2FkNbadahVUYwzdcIoK3JkY0%2Fimg.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;824&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://localhost:3000/posts/pre-rendering&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음은 지금까지의 과정을 요약한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;1054&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3qP1/btq2ksG7R3Y/tnzKGYseKrAQ4RkI7XKtUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3qP1/btq2ksG7R3Y/tnzKGYseKrAQ4RkI7XKtUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3qP1/btq2ksG7R3Y/tnzKGYseKrAQ4RkI7XKtUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3qP1%2Fbtq2ksG7R3Y%2FtnzKGYseKrAQ4RkI7XKtUK%2Fimg.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;1054&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Markdown 사용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마크다운 문법을 사용하기 위해 &lt;b&gt;remark&lt;/b&gt;라이브러리를 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618056151962&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install remark remark-html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;lib/posts.js&lt;/b&gt;에서 다음과 같이 import시킨 후, &lt;b&gt;getPostData&lt;/b&gt;함수를 수정해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618056338664&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import remark from &quot;remark&quot;;
import html from &quot;remark-html&quot;;

...

export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, &quot;utf8&quot;);

  // 포스트의 메타데이터를 파싱하기 위해 gray-matter 사용
  const matterResult = matter(fileContents);

  // remark 를 사용하여 마크다운을 HTML로 변환
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content)
  const contentHtml = processedContent.toString()

  // 데이터를 id 와 병합시킨다.
  return {
    id,
    contentHtml,
    ...matterResult.data,
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;remark&lt;/b&gt;를 사용하기 위해 &lt;b&gt;getPostData&lt;/b&gt;함수에서 &lt;b&gt;async await&lt;/b&gt;을 사용했다. 따라서 &lt;b&gt;pages/posts/[id].js&lt;/b&gt;의 &lt;b&gt;getStaticProps&lt;/b&gt;에서 &lt;b&gt;getPostData&lt;/b&gt;를 호출할 때 &lt;b&gt;await&lt;/b&gt;를 붙여 호출한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618056500307&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export async function getStaticProps({ params }) {
  const postData = await getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 &lt;b&gt;pages/posts/[id].js&lt;/b&gt;의 &lt;b&gt;Post&lt;/b&gt;컴포넌트에서 &lt;b&gt;contentHtml&lt;/b&gt;을 렌더링하기 위해 &lt;b&gt;dangerouslySetInnerHTML&lt;/b&gt;를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618056583686&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Post({ postData }) {
  return (
    &amp;lt;Layout&amp;gt;
      {postData.title}
      &amp;lt;br /&amp;gt;
      {postData.id}
      &amp;lt;br /&amp;gt;
      {postData.date}
      &amp;lt;br /&amp;gt;
      &amp;lt;div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} /&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼 결과를 확인 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1472&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YClS1/btq2h4fUJxl/CVj6MKr2MEGxGGVrNEqa0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YClS1/btq2h4fUJxl/CVj6MKr2MEGxGGVrNEqa0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YClS1/btq2h4fUJxl/CVj6MKr2MEGxGGVrNEqa0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYClS1%2Fbtq2h4fUJxl%2FCVj6MKr2MEGxGGVrNEqa0k%2Fimg.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1472&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로는 포스트 페이지에 스타일도 추가하고 &lt;b&gt;title&lt;/b&gt;도 추가하면서 더 다듬어 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;[id].js&lt;/b&gt;에 &lt;b&gt;title&lt;/b&gt;을 추가하기 위해 &lt;b&gt;next/head&lt;/b&gt;에서 &lt;b&gt;Head&lt;/b&gt;컴포넌트를 추가하고 다음과 같이 &lt;b&gt;Post&lt;/b&gt;컴포넌트를 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618057309239&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Post({ postData }) {
  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;{postData.title}&amp;lt;/title&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;br /&amp;gt;
      {postData.id}
      &amp;lt;br /&amp;gt;
      {postData.date}
      &amp;lt;br /&amp;gt;
      &amp;lt;div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} /&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;포스트 날짜를 나타내기 위해 &lt;b&gt;date-fns&lt;/b&gt;라이브러리를 설치해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618057366281&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install date-fns&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;components/date.js&lt;/b&gt;파일을 만들어 &lt;b&gt;Date&lt;/b&gt;컴포넌트를 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618057428328&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { parseISO, format } from &quot;date-fns&quot;;

export default function Date({ dateString }) {
  const date = parseISO(dateString);
  return &amp;lt;time dateTime={dateString}&amp;gt;{format(date, &quot;LLLL d, yyyy&quot;)}&amp;lt;/time&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages/posts/[id].js&lt;/b&gt;에서 &lt;b&gt;Date&lt;/b&gt;컴포넌트를 import한 후, 다음과 같이 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618057592517&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Post({ postData }) {
  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;{postData.title}&amp;lt;/title&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;br /&amp;gt;
      {postData.id}
      &amp;lt;br /&amp;gt;
      &amp;lt;Date dateString={postData.date} /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} /&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 이전에 했던 것처럼 &lt;b&gt;styles/utils.module.css&lt;/b&gt;파일을 사용하여 CSS를 추가해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;[id].js&lt;/b&gt;에서 CSS파일을 import시킨 후, &lt;b&gt;Post&lt;/b&gt;컴포넌트를 다음과 같이 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618057913024&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Post({ postData }) {
  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;{postData.title}&amp;lt;/title&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;article&amp;gt;
        &amp;lt;h1 className={utilStyles.headingXl}&amp;gt;{postData.title}&amp;lt;/h1&amp;gt;
        &amp;lt;div className={utilStyles.lightText}&amp;gt;
          &amp;lt;Date dateString={postData.date} /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} /&amp;gt;
      &amp;lt;/article&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;696&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQoOw/btq2hD3WHFC/lLHYriYxAO9DoX9c7khbLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQoOw/btq2hD3WHFC/lLHYriYxAO9DoX9c7khbLk/img.png&quot; data-alt=&quot;http://localhost:3000/posts/pre-rendering&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQoOw/btq2hD3WHFC/lLHYriYxAO9DoX9c7khbLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQoOw%2Fbtq2hD3WHFC%2FlLHYriYxAO9DoX9c7khbLk%2Fimg.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;696&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://localhost:3000/posts/pre-rendering&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;포스트 페이지를 다듬었으니 홈에 있는 다음은 인덱스 페이지를 다듬을 차례이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, 각 포스트 페이지에 Link컴포넌트를 통해 링크를 걸어주자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages/index.js&lt;/b&gt;에서 &lt;b&gt;Link&lt;/b&gt;와 &lt;b&gt;Date&lt;/b&gt;를 가져온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618058114242&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Link from &quot;next/link&quot;;
import Date from &quot;../components/date&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;Home&lt;/b&gt;컴포넌트에서 &lt;b&gt;li&lt;/b&gt;태그에 있는 내용을 다음과 같이 바꿔준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618058260844&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;li className={utilStyles.listItem} key={id}&amp;gt;
  &amp;lt;Link href={`/posts/${id}`}&amp;gt;
    &amp;lt;a&amp;gt;{title}&amp;lt;/a&amp;gt;
  &amp;lt;/Link&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;small className={utilStyles.lightText}&amp;gt;
    &amp;lt;Date dateString={date} /&amp;gt;
  &amp;lt;/small&amp;gt;
&amp;lt;/li&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1212&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTnfue/btq2gYndevz/6LtCsqZk0e0cZAktn8uteK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTnfue/btq2gYndevz/6LtCsqZk0e0cZAktn8uteK/img.png&quot; data-alt=&quot;http://localhost:3000/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTnfue/btq2gYndevz/6LtCsqZk0e0cZAktn8uteK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTnfue%2Fbtq2gYndevz%2F6LtCsqZk0e0cZAktn8uteK%2Fimg.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1212&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://localhost:3000/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>dynamic routes</category>
      <category>next.js</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/122</guid>
      <comments>https://gingerkang.tistory.com/122#entry122comment</comments>
      <pubDate>Sat, 10 Apr 2021 21:44:46 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Pre-rendering과 Data Fetching</title>
      <link>https://gingerkang.tistory.com/121</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F4ZnF/btq2gEu1rBp/SJ9diVV5DF9AdBuQrVIKg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F4ZnF/btq2gEu1rBp/SJ9diVV5DF9AdBuQrVIKg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F4ZnF/btq2gEu1rBp/SJ9diVV5DF9AdBuQrVIKg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF4ZnF%2Fbtq2gEu1rBp%2FSJ9diVV5DF9AdBuQrVIKg1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;i&gt;이 글은 &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;의 공식 튜토리얼 문서를 참고하여 작성한 글입니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://next-learn-starter.vercel.app/&quot;&gt;예시&lt;/a&gt;와 같은 블로그를 만들고 싶지만, 아직까지는 블로그 내용을 추가하지 않았다. 이제 블로그에 필요한 외부 데이터를 애플리케이션에 가져오는 방법을 알아보자. 여기서는 블로그 내용이 파일 시스템에 저장되지만, 배포를 위해선 콘텐츠가 다른 곳(예: 데이터베이스, Headless CMS)에 저장되어 있는 경우 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아래는 우리가 이 글에서 배울 것들이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js의 pre-rendering&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;pre-rendering의 두가지 형식: Static Generation과 Server-side Rendering&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;데이터가 있을 때 Static Generation, 데이터가 없을 때의 Static Generation&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;getStaticProps&lt;/b&gt;와 사용법&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해당 글을 읽고 따라오기 위해선 &lt;a href=&quot;https://gingerkang.tistory.com/120&quot;&gt;이전글&lt;/a&gt;과 &lt;a href=&quot;https://nextjs.org/learn/basics/assets-metadata-css/polishing-layout&quot;&gt;공식 튜토리얼&lt;/a&gt;을 참고하여 공식 문서의 polishing layout챕터까지 끝낸 상태여야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 서버를 실행시키면 다음과 같은 결과가 나올 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B1Ofp/btq18prcIun/n6MlKZbppmeWs5tbWo6eak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B1Ofp/btq18prcIun/n6MlKZbppmeWs5tbWo6eak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B1Ofp/btq18prcIun/n6MlKZbppmeWs5tbWo6eak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB1Ofp%2Fbtq18prcIun%2Fn6MlKZbppmeWs5tbWo6eak%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5kb7T/btq2cY0cESf/kB2nwKL1lWBmK3yiuCg5XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5kb7T/btq2cY0cESf/kB2nwKL1lWBmK3yiuCg5XK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5kb7T/btq2cY0cESf/kB2nwKL1lWBmK3yiuCg5XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5kb7T%2Fbtq2cY0cESf%2FkB2nwKL1lWBmK3yiuCg5XK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Pre-rendering&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;data fetching에 대해 얘기하기 전에, Next.js의 가장 중요한 컨셉 중 하나인 Pre-rendering에 대해 얘기하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;기본적으로, Next.js는 모든 페이지를 pre-render한다. 이 말은 즉슨, client-side 자바스크립트와는 다르게 Next.js는 각 페이지에 대해 미리 HTML을 만들어 놓는다. 이러한 Pre-rendering로 인해 좋은 성능과 SEO를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;생성된 HTML에는 최소한으로 필요한 자바스크립트 코드가 있다. 페이지가 브라우저에 의해 로드될 때, 그 자바스크립트 코드가 실행되고 페이지를 동적이게 만들어준다(이 과정을 hydration이라고 부른다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVk3FT/btq17G00INP/RWclGKW91FqnscK7okNQC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVk3FT/btq17G00INP/RWclGKW91FqnscK7okNQC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVk3FT/btq17G00INP/RWclGKW91FqnscK7okNQC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVk3FT%2Fbtq17G00INP%2FRWclGKW91FqnscK7okNQC1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1gUg/btq16ifyjmH/iKrQFnJ4NTlxf0y0ZKZEQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1gUg/btq16ifyjmH/iKrQFnJ4NTlxf0y0ZKZEQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1gUg/btq16ifyjmH/iKrQFnJ4NTlxf0y0ZKZEQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1gUg%2Fbtq16ifyjmH%2FiKrQFnJ4NTlxf0y0ZKZEQK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이는 Server-side Rendering의 특징이기도 하다. 이전에 클라이언트 사이드 렌더링과 서버 사이드 렌더링에 대해 다룬 &lt;a href=&quot;https://gingerkang.tistory.com/116&quot;&gt;포스트&lt;/a&gt;가 있으니 참고하면 더 이해가 잘 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Pre-rendering의 형식&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js는 &lt;span style=&quot;color: #333333;&quot;&gt;pre-rendering을 Static Generation과 Server-side Rendering, 이 두 가지 형식으로 제공한다. 이 둘의 차이점은 언제 HTML이 생성되는지 이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Static Generation은 빌드 타임에 HTML을 생성하는 pre-rendering 방법이다. pre-rendered된 HTML은 각 요청마다 재사용이 가능하다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Server-side Rendering은 각 요청마다 HTML을 생성하는 pre-rendering 방법이다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/comQnG/btq2hYTq75S/ND9EIvbLLyKkkAkszNKqI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/comQnG/btq2hYTq75S/ND9EIvbLLyKkkAkszNKqI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/comQnG/btq2hYTq75S/ND9EIvbLLyKkkAkszNKqI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcomQnG%2Fbtq2hYTq75S%2FND9EIvbLLyKkkAkszNKqI1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MJlk2/btq2hrPfbVi/hig0RjkcjQkXZxcSKo2Ei1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MJlk2/btq2hrPfbVi/hig0RjkcjQkXZxcSKo2Ei1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MJlk2/btq2hrPfbVi/hig0RjkcjQkXZxcSKo2Ei1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMJlk2%2Fbtq2hrPfbVi%2Fhig0RjkcjQkXZxcSKo2Ei1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;i&gt;&lt;b&gt;npm run dev&lt;/b&gt;나 &lt;b&gt;yarn dev&lt;/b&gt;를 통해 개발을 하는 동안에는 Static Generation 방식을 사용하고 있다해도 모든 페이지가 각 요청마다 pre-rendering 된다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기서 중요한 점은, Next.js는 각 페이지마다 우리에게 어떠한 pre-rendering 방식을 사용할지 선택할 수 있게 해주는 것이다. 따라서 특정 페이지에는 Static Genration 방식을, 나머지 페이지에는 Server-side Rendering 방식을 사용할 수 있는 &quot;hybrid&quot;앱을 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1lE8z/btq2eNeWfwF/veteUTbYnP9YMrQsDbkO70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1lE8z/btq2eNeWfwF/veteUTbYnP9YMrQsDbkO70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1lE8z/btq2eNeWfwF/veteUTbYnP9YMrQsDbkO70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1lE8z%2Fbtq2eNeWfwF%2FveteUTbYnP9YMrQsDbkO70%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Static Generation v.s. Server-side Rendering&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js 공식 문서에서는 가능하면 Static Generation 방식을 사용하기를 권장하고 있다. 왜냐하면 페이지가 한번 빌드되어 CDN에 의해 제공이 될 수 있으며 매번 페이지 요청을 하는 것보다 렌더링하는 속도가 빠르기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이러한 Static Generation은 다음과 같은 상황에서 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마케팅 페이지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;블로그 포스트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;E-commerce 상품 리스트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각종 문서 페이지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;페이지를 제작할 때 &quot;사용자의 요청보다 먼저 페이지가 렌더링되어도 되는가?&quot; 에 대한 질문을 자신에게 던져보고, 대답이 &quot;응&quot; 이라면 Static Generation을 선택하는게 좋을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;반대로, 데이터의 업데이트가 많거나 사용자의 요청마다 매번 바뀌는 페이지라면 별로 좋은 선택이 아닐 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막에 설명한 상황에서는 Server-side Rendering 을 사용할 수 있다. 조금 느릴 순 있지만 페이지는 항상 최신의 상태를 유지한다. 아니면 pre-rendering을 생략하고 client-side JavaScript를 통해 보여줄 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Static Generation&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Static Generation은 데이터를 포함해도 되고, 포함하지 않아도 수행이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;지금까지는 아직 외부의 데이터를 fetching에서 사용하지 않았다. 이러한 페이지들은 앱이 빌드될 때 자동적으로 정적 생성될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uV1Er/btq2dHTuyis/m8556lTKzUzTwcNc3asr1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uV1Er/btq2dHTuyis/m8556lTKzUzTwcNc3asr1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uV1Er/btq2dHTuyis/m8556lTKzUzTwcNc3asr1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuV1Er%2Fbtq2dHTuyis%2Fm8556lTKzUzTwcNc3asr1K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그러나 몇몇의 페이지들에서 외부에서 데이터를 fetching 해오지 않으면, 렌더링이 안되는 경우가 있다. 예를 들어 파일 시스템에 접근하거나, 외부 API를 사용하거나, 데이터베이스에 쿼리를 날릴 때이다. Next.js는 이러한 경우(데이터를 사용한 정적 생성)도 지원하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2x68g/btq2gCRotnA/rmjSbnVQDGmsAkTHf15PW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2x68g/btq2gCRotnA/rmjSbnVQDGmsAkTHf15PW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2x68g/btq2gCRotnA/rmjSbnVQDGmsAkTHf15PW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2x68g%2Fbtq2gCRotnA%2FrmjSbnVQDGmsAkTHf15PW0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getStaticProps&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 과정을 가능하게 하는 것이 &lt;b&gt;getStaticProps&lt;/b&gt;이다. Next.js 프로젝트에서 한 페이지 컴포넌트를 export할 때, &lt;b&gt;getStaticProps&lt;/b&gt;함수를 &lt;b&gt;async&lt;/b&gt;형태로 만들어 같이 export해준다. &lt;b&gt;getStaticProps&lt;/b&gt;는 빌드 타임에 동작하며 함수 내에서 외부 데이터를 가져와 페이지 컴포넌트에 넘겨줄 수 있다. 기본적인 사용 형태는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617961984113&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Home(props) { ... }

export async function getStaticProps() {
  // 파일 시스템, API, DB 데이터와 같은 외부 데이터를 가져온다.
  const data = ...

  // props 의 'key' 값은
  // Home 컴포넌트에 전달될 것이다.
  return {
    props: ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;getStaticProps&lt;/b&gt;을 작성하면, Next.js에 이렇게 말할 수 있다. &quot;야, 이 페이지는 어떠한 데이터들에 의존성을 갖고 있으니까 pre-render하기 전에 이것 먼저 해결하고 해!&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼 이제 &lt;b&gt;getStaticProps&lt;/b&gt;를 사용하여 만들어둔 블로그에 포스팅을 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 최상위에 &lt;b&gt;posts&lt;/b&gt;폴더를 만든다(이는 &lt;b&gt;pages/posts&lt;/b&gt;와는 다른 폴더이다).&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로 &lt;b&gt;posts&lt;/b&gt;폴더 안에 &lt;b&gt;pre-rendering.md&lt;/b&gt;파일과 &lt;b&gt;ssg-ssr.md&lt;/b&gt;파일을 만들어주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617962282975&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pre-rendering.md

---
title: 'Two Forms of Pre-rendering'
date: '2020-01-01'
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a &quot;hybrid&quot; Next.js app by using Static Generation for most pages and using Server-side Rendering for others.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617962300666&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ssg-ssr.md

---
title: 'When to Use Static Generation v.s. Server-side Rendering'
date: '2020-01-02'
---

We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.

You can use Static Generation for many types of pages, including:

- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation

You should ask yourself: &quot;Can I pre-render this page **ahead** of a user's request?&quot; If the answer is yes, then you should choose Static Generation.

On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.

In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 마크다운 파일 상단에는 &lt;b&gt;title&lt;/b&gt;과 &lt;b&gt;date&lt;/b&gt;라는 메타데이터 섹션이 있는 것을 볼 수 있다. 이것은 YAML Front Matter라고 불리우며, gray-matter라는 라이브러리를 사용하여 파싱될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 이제 &lt;b&gt;index.js&lt;/b&gt;페이지를 수정해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각 마크다운 파일에서 &lt;b&gt;title&lt;/b&gt;, &lt;b&gt;date&lt;/b&gt;와 파일 명을 가져온다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해당 데이터들을 &lt;b&gt;index.js&lt;/b&gt;페이지에서 &lt;b&gt;date&lt;/b&gt;를 기준으로 정렬시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lxZ3t/btq2hJCdR7x/zvoZHrBd7wsM9McYk6tc7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lxZ3t/btq2hJCdR7x/zvoZHrBd7wsM9McYk6tc7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lxZ3t/btq2hJCdR7x/zvoZHrBd7wsM9McYk6tc7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlxZ3t%2Fbtq2hJCdR7x%2FzvoZHrBd7wsM9McYk6tc7K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getStaticProps 구현&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, 마크다운 파일의 메타데이터를 파싱하기 위해 gray-matter를 설치해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617962602485&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install gray-matter&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로 파일 시스템에서 데이터를 가져오기 위해 간단한 라이브러리를 만들어보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 최상위에 &lt;b&gt;lib&lt;/b&gt;폴더를 만든다. &lt;b&gt;lib&lt;/b&gt;안에 &lt;b&gt;posts.js&lt;/b&gt;파일을 만들어 다음과 같이 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617962702560&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'

const postsDirectory = path.join(process.cwd(), 'posts')

export function getSortedPostsData() {
  // posts 폴더 안에 있는 파일 명들을 가져온다. 
  const fileNames = fs.readdirSync(postsDirectory)
  const allPostsData = fileNames.map(fileName =&amp;gt; {
    // 파일 명에서 &quot;.md&quot; 확장자를 제거한다.
    const id = fileName.replace(/\.md$/, '')

    // 마크다운 파일을 읽는다.
    const fullPath = path.join(postsDirectory, fileName)
    const fileContents = fs.readFileSync(fullPath, 'utf8')

    // 메타데이터를 파싱하기 위해 gray-matter를 사용한다.
    const matterResult = matter(fileContents)

    // id와 데이터를 한 객체로 묶는다.
    return {
      id,
      ...matterResult.data
    }
  })
  // date 순으로 포스트를 정렬한다.
  return allPostsData.sort((a, b) =&amp;gt; {
    if (a.date &amp;lt; b.date) {
      return 1
    } else {
      return -1
    }
  })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 &lt;b&gt;index.js&lt;/b&gt;에서 &lt;b&gt;getStaticProps&lt;/b&gt;를 작성하고 그 안에서 &lt;b&gt;getSortedPostsData&lt;/b&gt;를 import해주어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;index.js&lt;/b&gt;에서 다음 코드를 &lt;b&gt;Home&lt;/b&gt;컴포넌트 위에 작성해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617963102730&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { getSortedPostsData } from '../lib/posts'

export async function getStaticProps() {
  const allPostsData = getSortedPostsData()
  return {
    props: {
      allPostsData
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;getStaticProps&lt;/b&gt;에서 리턴하는 &lt;b&gt;props&lt;/b&gt;객체의 &lt;b&gt;allPostsData&lt;/b&gt;는 &lt;b&gt;Home&lt;/b&gt;컴포넌트에 prop으로 전달될 것이다. &lt;b&gt;Home&lt;/b&gt;에서는 다음과 같이 &lt;b&gt;allPostsData&lt;/b&gt;에 접근할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617963218257&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Home ({ allPostsData }) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;블로그 포스트를 화면에 나타내기 위해, &lt;b&gt;Home&lt;/b&gt;컴포넌트를 조금 수정해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617963340914&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Home({ allPostsData }) {
  return (
    &amp;lt;Layout home&amp;gt;
      {/* 기존의 코드는 그대로. */}

      {/* 아래 &amp;lt;section&amp;gt; 태그로 감싸진 부분을 추가해준다. */}
      &amp;lt;section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}&amp;gt;
        &amp;lt;h2 className={utilStyles.headingLg}&amp;gt;Blog&amp;lt;/h2&amp;gt;
        &amp;lt;ul className={utilStyles.list}&amp;gt;
          {allPostsData.map(({ id, date, title }) =&amp;gt; (
            &amp;lt;li className={utilStyles.listItem} key={id}&amp;gt;
              {title}
              &amp;lt;br /&amp;gt;
              {id}
              &amp;lt;br /&amp;gt;
              {date}
            &amp;lt;/li&amp;gt;
          ))}
        &amp;lt;/ul&amp;gt;
      &amp;lt;/section&amp;gt;
    &amp;lt;/Layout&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지 했다면 다음과 같은 결과를 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qjyK3/btq2hZLHUfP/4SRE3g19DPjaM3nv5hl8Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qjyK3/btq2hZLHUfP/4SRE3g19DPjaM3nv5hl8Ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qjyK3/btq2hZLHUfP/4SRE3g19DPjaM3nv5hl8Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqjyK3%2Fbtq2hZLHUfP%2F4SRE3g19DPjaM3nv5hl8Ik%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;아래는 우리가 코드로 작성한 과정을 정리한 그림이다. 파일 시스템에서 데이터(포스트)를 가져와 인덱스 페이지 리스트를 렌더링 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCPh8/btq2h3mYmtk/SBMD1Im6sCKIkKAWJE99Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCPh8/btq2h3mYmtk/SBMD1Im6sCKIkKAWJE99Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCPh8/btq2h3mYmtk/SBMD1Im6sCKIkKAWJE99Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCPh8%2Fbtq2h3mYmtk%2FSBMD1Im6sCKIkKAWJE99Qk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>Data Fetching</category>
      <category>getStaticProps</category>
      <category>next.js</category>
      <category>pre-rendering</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/121</guid>
      <comments>https://gingerkang.tistory.com/121#entry121comment</comments>
      <pubDate>Fri, 9 Apr 2021 19:27:22 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Assets, Metadata와 CSS</title>
      <link>https://gingerkang.tistory.com/120</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qm5tb/btq107Y2PBz/SKF5hBYv7aAX5gk0cvyuQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qm5tb/btq107Y2PBz/SKF5hBYv7aAX5gk0cvyuQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qm5tb/btq107Y2PBz/SKF5hBYv7aAX5gk0cvyuQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqm5tb%2Fbtq107Y2PBz%2FSKF5hBYv7aAX5gk0cvyuQk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 글은 &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;의 공식 튜토리얼 문서를 참고하여 작성한 글입니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://gingerkang.tistory.com/119&quot;&gt;이전글&lt;/a&gt;에서 만든 페이지는 아직 스타일링이 되어있지 않다. 이제 이 페이지에 CSS를 통해 스타일을 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js는 기본적으로 CSS와 Sass를 지원한다. 그리고 여기서는 CSS를 사용하겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 글에서는 Next.js가 어떻게 이미지와 같은 asset들을 관리하고 &lt;b&gt;&amp;lt;title&amp;gt;&lt;/b&gt;태그와 같은 metadata들을 관리하는지 알아볼 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Assets&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js는 이미지와 같은 정적 자산을 최상위인 &lt;b&gt;public&lt;/b&gt;디렉토리에서 제공이 가능하다. &lt;b&gt;public&lt;/b&gt;안에 있는 파일들은 &lt;b&gt;pages&lt;/b&gt;와 유사하게 애플리케이션 루트에서 접근이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한 이 &lt;b&gt;public&lt;/b&gt;디렉토리는 &lt;b&gt;robots.txt&lt;/b&gt;, Google Site Verification과 다른 정적 자산에도 유용하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, 프로필 사진을 다운로드 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;&lt;b&gt;public&lt;/b&gt;디렉토리에 &lt;b&gt;images&lt;/b&gt;디렉토리를 만들고 &lt;b&gt;.jpg&lt;/b&gt;포맷의 프로필 사진을 그 안에 저장한다. public디렉토리 안에 있는 사용하지 않는 SVG로고 파일은 삭제해도 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span&gt;그리고 기존의 방식대로, 프로필 사진을 다음과 같이 불러올 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617789855703&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;/images/profile.jpg&quot; alt=&quot;ginger&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 이 방법은 다음과 같이 따로 처리해주어야할 것이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;달라지는 화면 사이즈에 따라 반응형 처리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;써드파티 툴이나 라이브러리를 이용한 이미지 최적화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자가 viewport에 들어왔을 때에만 이미지를 로딩&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 밖에도 더 있을 것이다. 하지만 Next.js는 &lt;span style=&quot;color: #333333;&quot;&gt;이러한 문제들을 &lt;span style=&quot;color: #333333;&quot;&gt;해결해줄 수 있는 &lt;b&gt;Image&lt;/b&gt;컴포넌트를 제공하고 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Image 컴포넌트와 이미지 최적화&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;next/image&lt;/b&gt;는 모던 웹을 위해 좀 더 발전된 HTML &lt;b&gt;&amp;lt;img&amp;gt;&lt;/b&gt;요소의 확장이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;또한 Next.js는 이미지 최적화를 기본적으로 제공한다. 이를 통해 브라우저가 지원하는 경우 WebP와 같은 최신 포맷으로 이미지 사이즈를 조정하고 최적화할 수 있다. 사이즈가 큰 이미지를 작은 화면의 기기에 그대로 사용하는 경우를 피할 수 있으며, 향후 이미지 형식을 자동으로 채택하고 해당 형식을 지원하는 브라우저에 제공 할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자동 이미지 최적화는 어떠한 이미지 소스여도 동작이 되며, 심지어 이미지가 CMS와 같은 외부 데이터에서 제공되더라도 가능하다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Image 컴포넌트&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Next.js는 빌드할 때 이미지를 최적화하는 대신에, 사용자가 요청한 것에 대해서 최적화를 진행한다. 정적 사이트 생성기 및 정적 전용 솔루션과 달리 빌드 시간은 이미지 10개를 전송하든 1,000만 개를 전송하든 증가하지 않게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이미지는 기본적으로 lazy loading된다. 이 말은 페이지 로드 속도가 viewport 외부의 이미지에 의해 느려지지 않는다는 것이다. 즉 이미지는 viewport안에 있는 스크롤에 따라 로드된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예를 들어서 &lt;b&gt;next/image&lt;/b&gt;를 통해 아까 저장한 프로필 사진을 보여주는 페이지를 만들어보자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;원하는 &lt;b&gt;height&lt;/b&gt;와 &lt;b&gt;width&lt;/b&gt;값을 주는데, 이때 기존 이미지의 비율과 일치해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617791135677&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Image from &quot;next/image&quot;;

const Profile = () =&amp;gt; (
  &amp;lt;Image
    src=&quot;/images/profile.jpg&quot; // 프로필 사진 경로
    height={200}
    width={300}
    alt=&quot;ginger&quot;
  /&amp;gt;
);

export default Profile;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2E5us/btq14YG47ok/mjKHDBcEQAtbbSbhqURVPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2E5us/btq14YG47ok/mjKHDBcEQAtbbSbhqURVPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2E5us/btq14YG47ok/mjKHDBcEQAtbbSbhqURVPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2E5us%2Fbtq14YG47ok%2FmjKHDBcEQAtbbSbhqURVPk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Metadata&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 &lt;b&gt;&amp;lt;title&amp;gt;&lt;/b&gt;태그와 같은 페이지의 메타데이터를 바꾸고 싶을 땐 어떻게 할까?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&amp;lt;title&amp;gt;&lt;/b&gt;은 &lt;b&gt;&amp;lt;head&amp;gt;&lt;/b&gt;태그에 있기 때문에 Next.js에서 &lt;b&gt;&amp;lt;head&amp;gt;&lt;/b&gt;태그를 어떻게 수정하는지 알아야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages/index.js&lt;/b&gt;을 보면 다음과 같은 코드가 작성되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617791393137&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Head&amp;gt;
  &amp;lt;title&amp;gt;Create Next App&amp;lt;/title&amp;gt;
  &amp;lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&amp;gt;
&amp;lt;/Head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위의 코드를 보면 &lt;b&gt;&amp;lt;head&amp;gt;&lt;/b&gt;태그 대신에 &lt;b&gt;&amp;lt;Head&amp;gt;&lt;/b&gt;로 작성된 React 컴포넌트가 있는 것을 볼 수 있다. 이는 Next.js에서 기본적으로 제공하는 컴포넌트로, 페이지의 &lt;b&gt;&amp;lt;head&amp;gt;&lt;/b&gt;태그를 수정할 수 있게 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Head&lt;/b&gt;컴포넌트는 &lt;b&gt;next/head&lt;/b&gt;모듈에서 import하여 사용이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;/posts/first-post&lt;/b&gt;에 &lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;lt;title&amp;gt;&lt;/b&gt;이 아직 없기 때문에&amp;nbsp;&lt;/span&gt;추가해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages/posts/first-post.js&lt;/b&gt;파일을 열고 &lt;b&gt;Head&lt;/b&gt;를 import시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617791670382&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// first-post.js

import Head from &quot;next/head&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;FirstPost&lt;/b&gt;컴포넌트에 &lt;b&gt;Head&lt;/b&gt;를 넣어 다음과 같이 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617791742653&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function FirstPost() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;첫 번째 포스트&amp;lt;/title&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;h1&amp;gt;첫 포스트!&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;
        &amp;lt;Link href=&quot;/&quot;&amp;gt;
          &amp;lt;a&amp;gt;홈으로 돌아가기&amp;lt;/a&amp;gt;
        &amp;lt;/Link&amp;gt;
      &amp;lt;/h2&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 이제 서버를 실행시켜 확인해보면 다음과 같이 브라우저 탭에서 첫 번째 포스트라고 나오는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQn6hP/btq107x3JCf/yGvXg6FmHliFrZkmM8ksJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQn6hP/btq107x3JCf/yGvXg6FmHliFrZkmM8ksJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQn6hP/btq107x3JCf/yGvXg6FmHliFrZkmM8ksJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQn6hP%2Fbtq107x3JCf%2FyGvXg6FmHliFrZkmM8ksJK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CSS 스타일&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 CSS얘기를 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;index.js&lt;/b&gt;를 보면 알 수 있듯이 이미 어느정도의 스타일이 되어 있다. 스타일 코드는 다음과 같이 생겼을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617792086007&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style jsx&amp;gt;{`
  ...
`}&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 페이지는 styled-jsx라는 라이브러리를 사용하고 있다. 이는 &quot;CSS-in-JS&quot;라이브러리로, 리액트 컴포넌트에서 CSS를 작성할 수 있으며 각 CSS 스타일은 컴포넌트에 대해 스코프를 가지므로 다른 컴포넌트에 독립적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js는 기본적으로 styled-jsx를 지원하지만 styled-components나 emotion과 같은 다른 유명한 CSS-in-JS라이브러리를 사용하는 것도 물론 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Layout 컴포넌트&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, 모든 페이지에서 공유가 가능한 &lt;b&gt;Layout&lt;/b&gt;컴포넌트를 만들어보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 최상위 경로에 &lt;b&gt;components&lt;/b&gt;라는 디렉토리를 만들고 &lt;b&gt;layout.js&lt;/b&gt;파일을 만들어주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617792467187&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Layout({ children }) {
  return &amp;lt;div&amp;gt;{children}&amp;lt;/div&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;pages/posts/first-post.js&lt;/b&gt;를 열고 방금 만든 &lt;b&gt;Layout&lt;/b&gt;컴포넌트를 import해준다. 이때 &lt;b&gt;Layout&lt;/b&gt;컴포넌트는 다른 컴포넌트를 감싸는 형태로 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617792565564&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Link from &quot;next/link&quot;;
import Head from &quot;next/head&quot;;
import Layout from &quot;../../components/layout&quot;;

export default function FirstPost() {
  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;첫 번째 포스트&amp;lt;/title&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;h1&amp;gt;첫 포스트!&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;
        &amp;lt;Link href=&quot;/&quot;&amp;gt;
          &amp;lt;a&amp;gt;홈으로 돌아가기&amp;lt;/a&amp;gt;
        &amp;lt;/Link&amp;gt;
      &amp;lt;/h2&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 &lt;b&gt;Layout&lt;/b&gt;컴포넌트에 스타일을 더해보자. 이를 위해서 React컴포넌트에서 CSS파일을 import하게 해주는 CSS Modules를 사용할 것이다. 그리고 CSS Modules를 사용하기 위해선 CSS파일의 이름은 &lt;b&gt;.module.css&lt;/b&gt;로 끝나야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;components/layout.module.css&lt;/b&gt;파일을 만들고 다음과 같이 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617792720659&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
  background-color: yellowgreen;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;container&lt;/b&gt;클래스를 &lt;b&gt;layout.js&lt;/b&gt;에서 사용하기 위해선 몇 가지 해주어야 할 것이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CSS파일을 &lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;styles&lt;/b&gt;라고 이름을 할당하여&lt;/span&gt; import해온다. 그리고 &lt;b&gt;className&lt;/b&gt;를 &lt;b&gt;styles.container&lt;/b&gt;로 설정하여 사용한다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;layout.js&lt;/b&gt;를 열어 다음과 같이 코드를 수정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617792956870&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import styles from &quot;./layout.module.css&quot;;

export default function Layout({ children }) {
  return &amp;lt;div className={styles.container}&amp;gt;{children}&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 확인해보면 다음과 같이 스타일이 적용된 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tm6Cs/btq14ZsqCjK/hsFVvK5KMs1kEsT5H5z0o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tm6Cs/btq14ZsqCjK/hsFVvK5KMs1kEsT5H5z0o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tm6Cs/btq14ZsqCjK/hsFVvK5KMs1kEsT5H5z0o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftm6Cs%2Fbtq14ZsqCjK%2FhsFVvK5KMs1kEsT5H5z0o0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한 개발자 도구를 통해 &lt;b&gt;Layout&lt;/b&gt;컴포넌트를 보면 클래스 명이 다음과 같이 나오는 것을 볼 수 있다. &lt;b&gt;layout_container_...&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CDhVL/btq17Ye0LZQ/68XXSS18FU0738XxoTnTJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CDhVL/btq17Ye0LZQ/68XXSS18FU0738XxoTnTJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CDhVL/btq17Ye0LZQ/68XXSS18FU0738XxoTnTJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCDhVL%2Fbtq17Ye0LZQ%2F68XXSS18FU0738XxoTnTJK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이는 CSS Modules가 자동적으로 고유의 클래스 명을 생성해준 것이다. 이처럼 CSS Modules를 사용하면 고유의 클래스 명을 생각하는데에 힘을 쏟지 않아도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;게다가 Next.js의 코드 스플릿팅은 CSS Modules에서도 동작한다. 각 페이지마다 최소한의 CSS가 로드되는 것을 보장하고 이는 번들 사이즈를 줄여준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Global Styles&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CSS Modules은 컴포넌트별로 스타일링을 할 때 유용하다. 하지만 모든 페이지에 적용되는 스타일을 원할 때에는 어떻게 할까? Next.js는 이또한 제공해주고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;global CSS를 로드하기 위해 &lt;b&gt;pages/_app.js&lt;/b&gt;파일을 만들고 다음과 같이 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617793535310&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function App({ Component, pageProps }) {
  return &amp;lt;Component {...pageProps} /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 &lt;b&gt;App&lt;/b&gt;컴포넌트는 모든 다른 페이지에서 공통되는 최상위 구성 요소이다. 이 &lt;b&gt;App&lt;/b&gt;를 통해 페이지 간 이동을 할 때 상태를 유지할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;App&lt;/b&gt;컴포넌트를 추가했으면 서버를 재실행 할 필요가 있다. &lt;b&gt;Ctrl + C&lt;/b&gt;를 누르고 다시 서버를 실행시키자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617793692493&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Global CSS 추가&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js에서는 &lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;pages/_app.js&lt;/b&gt;에서&lt;/span&gt; global CSS를 import해줌으로써 사용할 수 있다. global CSS는 페이지의 모든 요소에 영향을 끼치기 때문에 &lt;b&gt;_app.js&lt;/b&gt; 이외의 경로에서는 global CSS를 import 할 수 없으니 주의한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 홈에서 &lt;b&gt;/posts/first-post&lt;/b&gt;로 이동하게끔 했다면, 홈의 global 스타일은 &lt;b&gt;/posts/first-post&lt;/b&gt;에 영향을 끼칠 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;global CSS파일은 어디에나 위치할 수 있으며, 아무런 이름이나 가질 수 있다. 그럼 이제 만들어보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 최상위에 &lt;b&gt;styles&lt;/b&gt;디렉토리를 만들고 그 안에 &lt;b&gt;global.css&lt;/b&gt;파일을 만들어준다. 그리고 다음과 같이 작성한다. 이는 몇몇 스타일을 초기화 시키고 &lt;b&gt;a&lt;/b&gt;태그의 색상을 변경할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617794147887&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  line-height: 1.6;
  font-size: 18px;
}

* {
  box-sizing: border-box;
}

a {
  color: #cc4137;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

img {
  max-width: 100%;
  display: block;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;마지막으로 &lt;b&gt;pages/_app.js&lt;/b&gt;에서 CSS파일을 import해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617794207807&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import '../styles/global.css'

export default function App({ Component, pageProps }) {
  return &amp;lt;Component {...pageProps} /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 잘 적용이 되었는지 확인해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmhyfz/btq103vTP8h/f11rxVZoWRdqc2CPhql0M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmhyfz/btq103vTP8h/f11rxVZoWRdqc2CPhql0M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmhyfz/btq103vTP8h/f11rxVZoWRdqc2CPhql0M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmhyfz%2Fbtq103vTP8h%2Ff11rxVZoWRdqc2CPhql0M1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;혹시 적용이 안된다면 서버를 재실행 했는지 확인하고 다시 서버를 실행시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>next.js</category>
      <category>Next.js CSS</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/120</guid>
      <comments>https://gingerkang.tistory.com/120#entry120comment</comments>
      <pubDate>Wed, 7 Apr 2021 20:41:39 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 페이지 간 이동하기</title>
      <link>https://gingerkang.tistory.com/119</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jcny6/btq12xiDxA6/pLKRTuYFLDrw4kyI1iyypk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jcny6/btq12xiDxA6/pLKRTuYFLDrw4kyI1iyypk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jcny6/btq12xiDxA6/pLKRTuYFLDrw4kyI1iyypk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJcny6%2Fbtq12xiDxA6%2FpLKRTuYFLDrw4kyI1iyypk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 글은&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;&lt;span style=&quot;color: #333333;&quot;&gt;의 공식 튜토리얼 문서를 참고하여 작성한 글입니다.&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이전 글에서 &lt;b&gt;Next.js&lt;/b&gt;프로젝트를 만들고 실행을 시켰다면 다음과 같은 화면을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rUOiV/btq103BeqRZ/Fr35NmMk2ek4wJLYEz5PW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rUOiV/btq103BeqRZ/Fr35NmMk2ek4wJLYEz5PW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rUOiV/btq103BeqRZ/Fr35NmMk2ek4wJLYEz5PW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrUOiV%2Fbtq103BeqRZ%2FFr35NmMk2ek4wJLYEz5PW0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 이 페이지를 조금 바꿔보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages/index.js&lt;/b&gt;에서 &lt;b&gt;&amp;lt;h1&amp;gt;&lt;/b&gt;태그 안에 있는 &quot;Welcome to&quot;를 &quot;Learn&quot;으로 바꾸고 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D4QWo/btq1WOSOYp0/8PGP4fOhpzNVngFuFra0wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D4QWo/btq1WOSOYp0/8PGP4fOhpzNVngFuFra0wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D4QWo/btq1WOSOYp0/8PGP4fOhpzNVngFuFra0wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD4QWo%2Fbtq1WOSOYp0%2F8PGP4fOhpzNVngFuFra0wk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;페이지간 이동하기&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 우리가 만든 애플리케이션에는 한 개의 페이지 밖에 없지만 웹 애플리케이션에는 보통 많은 페이지들이 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제부터 어떻게 더 많은 페이지들을 해당 애플리케이션에 추가시키는지 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해당 글에서 배울 것들&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;파일 시스템 라우팅을 이용한 새로운 페이지 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;페이지 간에 클라이언트 사이드 네비게이션을 가능하게 할 &lt;b&gt;Link&lt;/b&gt;컴포넌트의 사용법&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코드 스플릿팅과 프리페칭의 사용법&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Next.js에서의 페이지&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js에서의 페이지는, &lt;b&gt;pages&lt;/b&gt;디렉토리 안에 있는 파일에서 export한 React 컴포넌트이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;각 페이지들은 기본적으로 그들의 파일명과 연관된다. 예를 들어 &lt;b&gt;pages/index.js&lt;/b&gt;는 &lt;b&gt;/&lt;/b&gt;라우트이다. &lt;b&gt;pages/posts/first-post.js&lt;/b&gt;는 &lt;b&gt;/posts/first-post&lt;/b&gt;와 연결된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;우리 애플리케이션에는 이미 &lt;b&gt;pages/index.js&lt;/b&gt;파일이 있다. 이제 &lt;b&gt;pages/posts/first-post.js&lt;/b&gt;파일을 만들어보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;페이지는 &lt;b&gt;pages&lt;/b&gt;디렉토리 안에 만들어야 한다. &lt;b&gt;pages&lt;/b&gt;디렉토리 안에 &lt;b&gt;posts&lt;/b&gt;디렉토리를 하나 만들어주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;posts&lt;/b&gt;디렉토리 안에 &lt;b&gt;first-post.js&lt;/b&gt;파일을 만든다. &lt;b&gt;first-post.js&lt;/b&gt;의 내용은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617708341036&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function FirstPost() {
  return &amp;lt;h1&amp;gt;첫 포스트!&amp;lt;/h1&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴포넌트명은 아무것이나 될 수 있으나 export할 때 &lt;b&gt;default&lt;/b&gt;를 붙여주어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 서버를 실행시키고 &lt;b&gt;localhost:3000/posts/first-post&lt;/b&gt;를 방문해보자. 그러면 다음과 같은 화면을 보게 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B0Bew/btq1ZK9Yqym/TCcifULaATjc1S3EhDRHGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B0Bew/btq1ZK9Yqym/TCcifULaATjc1S3EhDRHGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B0Bew/btq1ZK9Yqym/TCcifULaATjc1S3EhDRHGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB0Bew%2Fbtq1ZK9Yqym%2FTCcifULaATjc1S3EhDRHGK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지가 Next.js에서 다른 페이지를 생성하는 방법이다. 간단하게 &lt;b&gt;pages&lt;/b&gt;디렉토리안에 URL경로가 될 파일을 생성해주기만 하면 된다. 이는 HTML과 PHP를 사용하여 개발하는 것과 비슷한 방법이다. 대신에 HTML을 React 컴포넌트에서 JSX를 사용하여 작성하는 것이 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제는 새로운 페이지로의 링크를 추가하고 이동해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Link 컴포넌트&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;웹사이트에서 페이지를 연결하고 이동할 때는 &lt;b&gt;&amp;lt;a&amp;gt;&lt;/b&gt;태그를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js에서는 &lt;b&gt;Link&lt;/b&gt;컴포넌트가 &lt;b&gt;&amp;lt;a&amp;gt;&lt;/b&gt;태그를 래핑하여 사용된다. &lt;b&gt;next/link&lt;/b&gt;의 &lt;b&gt;&amp;lt;Link&amp;gt;&lt;/b&gt;컴포넌트는 애플리케이션에서 페이지 간에 클라이언트 사이드 네비게이션이 가능하게 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저, &lt;b&gt;pages/index.js&lt;/b&gt;를 열고 &lt;b&gt;next/link&lt;/b&gt;에서 &lt;b&gt;Link&lt;/b&gt;컴포넌트를 import해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617708841013&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Link from &quot;next/link&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 &lt;b&gt;h1&lt;/b&gt;태그를 다음과 같이 바꿔준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617708934324&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h1 className=&quot;title&quot;&amp;gt;
  Read{' '}
  &amp;lt;Link href=&quot;/posts/first-post&quot;&amp;gt;
    &amp;lt;a&amp;gt;this page!&amp;lt;/a&amp;gt;
  &amp;lt;/Link&amp;gt;
&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;{' '}는 공백 문자를 추가해준다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다음으로 &lt;b&gt;pages/posts/first-posts.js&lt;/b&gt;를 열어 다음과 같이 코드를 변경해주자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617709060974&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Link from &quot;next/link&quot;;

export default function FirstPost() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;첫 포스트!&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;
        &amp;lt;Link href=&quot;/&quot;&amp;gt;
          &amp;lt;a&amp;gt;홈으로 돌아가기&amp;lt;/a&amp;gt;
        &amp;lt;/Link&amp;gt;
      &amp;lt;/h2&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;코드에서 보이는대로, &lt;b&gt;Link&lt;/b&gt;컴포넌트는 &lt;b&gt;&amp;lt;a&amp;gt;&lt;/b&gt;태그와 사용법이 비슷하다. &lt;b&gt;&amp;lt;a href=&quot;...&quot;&amp;gt;&lt;/b&gt;대신에 &lt;b&gt;&amp;lt;Link href=&quot;...&quot;&amp;gt;&lt;/b&gt;로 사용하고, &lt;b&gt;Link&lt;/b&gt;컴포넌트 안에 &lt;b&gt;&amp;lt;a&amp;gt;&lt;/b&gt;태그를 넣어주는 것이 다른 점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한 주의해야할 점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js 앱 외부의 페이지로 링크를 걸 때는, &lt;b&gt;Link&lt;/b&gt;없이 &lt;b&gt;a&lt;/b&gt;태그만을 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;className&lt;/b&gt;과 같은 속성을 추가할 때는 &lt;b&gt;Link&lt;/b&gt;태그에 추가하지 않고 &lt;b&gt;a&lt;/b&gt;태그에 추가해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼 애플리케이션에서 확인해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmuQcE/btq1We5kINW/kJopO0BIazlMJBndhIYcbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmuQcE/btq1We5kINW/kJopO0BIazlMJBndhIYcbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmuQcE/btq1We5kINW/kJopO0BIazlMJBndhIYcbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmuQcE%2Fbtq1We5kINW%2FkJopO0BIazlMJBndhIYcbK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;this page&lt;/b&gt;가 &lt;b&gt;first-post&lt;/b&gt;로의 링크가 되었고, 이를 클릭하면 &lt;b&gt;first-post&lt;/b&gt;로 페이지가 넘어간다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b40IIf/btq1XkKDjTU/GokdJJB291zHn0OmPBqco0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b40IIf/btq1XkKDjTU/GokdJJB291zHn0OmPBqco0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b40IIf/btq1XkKDjTU/GokdJJB291zHn0OmPBqco0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb40IIf%2Fbtq1XkKDjTU%2FGokdJJB291zHn0OmPBqco0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Client-Side Navigation&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Link&lt;/b&gt;컴포넌트는 Next.js 앱에서 두 페이지 간 클라이언트 사이드 네비게이션을 가능하게 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;클라이언트 사이드 네비게이션이 의미하는 것은 기존의 브라우저가 사용하는 기본 네비게이션 기능보다 빠른 자바스크립트를 사용하여 페이지를 변경하는 것을 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이게 무슨 말인지는&amp;nbsp; 간단하게 확인이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;개발자 도구를 열고 &lt;b&gt;html&lt;/b&gt;의 &lt;b&gt;background&lt;/b&gt;속성을 hotpink로 바꾼다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 페이지에서 만든 링크를 클릭해본다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;hotpink로 설정한 배경 색상이 페이지가 이동해도 지속되는 것을 볼 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgek9v/btq1Ykqb75I/BkO8kE8XRSWrmOGs4UKyJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgek9v/btq1Ykqb75I/BkO8kE8XRSWrmOGs4UKyJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgek9v/btq1Ykqb75I/BkO8kE8XRSWrmOGs4UKyJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgek9v%2Fbtq1Ykqb75I%2FBkO8kE8XRSWrmOGs4UKyJK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkOJZM/btq11ztaiED/g938EHV42V3lQbOYNFqKek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkOJZM/btq11ztaiED/g938EHV42V3lQbOYNFqKek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkOJZM/btq11ztaiED/g938EHV42V3lQbOYNFqKek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkOJZM%2Fbtq11ztaiED%2Fg938EHV42V3lQbOYNFqKek%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이것은 브라우저가 전체 페이지를 로딩하지 않고 클라이언트 사이드 네비게이션이 동작하고 있다는 말이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약 &lt;b&gt;Link&lt;/b&gt;컴포넌트를 사용하지 않고 기존의 방식대로 &lt;b&gt;a&lt;/b&gt;태그를 사용했다면 페이지를 이동할 때 브라우저가 전체를 새로고침하기 때문에 배경화면이 흰색으로 초기화 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Code splitting and prefetching&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js는 자동으로 코드 스플릿팅을 해준다. 따라서 각 페이지는 정말 필요한 자원들만 로드하게 된다. 이것은 홈페이지가 렌더링될 때, 다른 페이지의 코드들은 로드되지 않는 것을 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이러한 특징은 만일 페이지가 수백개가 있다고 해도 홈페이지의 빠른 로딩 속도를 보장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;내가 요청한 페이지의 코드만 로딩이 된다는 것은 각 페이지가 독립적이라는 말도 된다. 만약 한 페이지에서 에러가 발생했다고 해도, 애플리케이션의 다른 페이지들은 계속 동작할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;게다가, &lt;span style=&quot;color: #333333;&quot;&gt;Next.js는 자동으로 백그라운드에서 링크된 페이지의 코드를 프리페치하기 때문에&amp;nbsp;&lt;/span&gt;&lt;b&gt;Link&lt;/b&gt;컴포넌트를 사용할 때마다 브라우저의 &lt;b&gt;viewport&lt;/b&gt;에 나타나게 된다. 링크를 클릭하면 링크된 페이지에 대한 코드가 이미 백그라운드에 로드되어 페이지 전환이 거의 바로 이루어지게 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요약&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Next.js는 코드 스플릿팅, 클라이언트 사이드 네비게이션, 프리페칭을 통해 우리의 애플리케이션을 자동으로 최적화 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;pages&lt;/b&gt;폴더 안에 파일을 만드는 것으로 라우트 시스템을 편하게 구현할 수 있고, &lt;b&gt;Link&lt;/b&gt;컴포넌트를 사용하여 간단하게 페이지 전환을 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>Client-side navigation</category>
      <category>Code Splitting</category>
      <category>next.js</category>
      <category>Next.js 페이지 이동</category>
      <category>prefetching</category>
      <author>생강강</author>
      <guid isPermaLink="true">https://gingerkang.tistory.com/119</guid>
      <comments>https://gingerkang.tistory.com/119#entry119comment</comments>
      <pubDate>Tue, 6 Apr 2021 21:04:06 +0900</pubDate>
    </item>
  </channel>
</rss>