이 글은 Next.js의 공식 튜토리얼 문서를 참고하여 작성한 글입니다.


 

예시와 같은 블로그를 만들고 싶지만, 아직까지는 블로그 내용을 추가하지 않았다. 이제 블로그에 필요한 외부 데이터를 애플리케이션에 가져오는 방법을 알아보자. 여기서는 블로그 내용이 파일 시스템에 저장되지만, 배포를 위해선 콘텐츠가 다른 곳(예: 데이터베이스, Headless CMS)에 저장되어 있는 경우 동작한다.

아래는 우리가 이 글에서 배울 것들이다.

  • Next.js의 pre-rendering
  • pre-rendering의 두가지 형식: Static Generation과 Server-side Rendering
  • 데이터가 있을 때 Static Generation, 데이터가 없을 때의 Static Generation
  • getStaticProps와 사용법

해당 글을 읽고 따라오기 위해선 이전글공식 튜토리얼을 참고하여 공식 문서의 polishing layout챕터까지 끝낸 상태여야 한다.

그리고 서버를 실행시키면 다음과 같은 결과가 나올 것이다.

Pre-rendering

data fetching에 대해 얘기하기 전에, Next.js의 가장 중요한 컨셉 중 하나인 Pre-rendering에 대해 얘기하고자 한다.

 

기본적으로, Next.js는 모든 페이지를 pre-render한다. 이 말은 즉슨, client-side 자바스크립트와는 다르게 Next.js는 각 페이지에 대해 미리 HTML을 만들어 놓는다. 이러한 Pre-rendering로 인해 좋은 성능과 SEO를 얻을 수 있다.

 

생성된 HTML에는 최소한으로 필요한 자바스크립트 코드가 있다. 페이지가 브라우저에 의해 로드될 때, 그 자바스크립트 코드가 실행되고 페이지를 동적이게 만들어준다(이 과정을 hydration이라고 부른다.)

이는 Server-side Rendering의 특징이기도 하다. 이전에 클라이언트 사이드 렌더링과 서버 사이드 렌더링에 대해 다룬 포스트가 있으니 참고하면 더 이해가 잘 될 것이다.

 

Pre-rendering의 형식

Next.js는 pre-rendering을 Static Generation과 Server-side Rendering, 이 두 가지 형식으로 제공한다. 이 둘의 차이점은 언제 HTML이 생성되는지 이다.

 

Static Generation은 빌드 타임에 HTML을 생성하는 pre-rendering 방법이다. pre-rendered된 HTML은 각 요청마다 재사용이 가능하다.

Server-side Rendering은 각 요청마다 HTML을 생성하는 pre-rendering 방법이다.

 

 

npm run devyarn dev를 통해 개발을 하는 동안에는 Static Generation 방식을 사용하고 있다해도 모든 페이지가 각 요청마다 pre-rendering 된다.

 

여기서 중요한 점은, Next.js는 각 페이지마다 우리에게 어떠한 pre-rendering 방식을 사용할지 선택할 수 있게 해주는 것이다. 따라서 특정 페이지에는 Static Genration 방식을, 나머지 페이지에는 Server-side Rendering 방식을 사용할 수 있는 "hybrid"앱을 만들 수 있다.

 

 

Static Generation v.s. Server-side Rendering

Next.js 공식 문서에서는 가능하면 Static Generation 방식을 사용하기를 권장하고 있다. 왜냐하면 페이지가 한번 빌드되어 CDN에 의해 제공이 될 수 있으며 매번 페이지 요청을 하는 것보다 렌더링하는 속도가 빠르기 때문이다.

 

이러한 Static Generation은 다음과 같은 상황에서 사용할 수 있다.

  • 마케팅 페이지
  • 블로그 포스트
  • E-commerce 상품 리스트
  • 각종 문서 페이지

페이지를 제작할 때 "사용자의 요청보다 먼저 페이지가 렌더링되어도 되는가?" 에 대한 질문을 자신에게 던져보고, 대답이 "응" 이라면 Static Generation을 선택하는게 좋을 것이다.

반대로, 데이터의 업데이트가 많거나 사용자의 요청마다 매번 바뀌는 페이지라면 별로 좋은 선택이 아닐 것이다.

 

마지막에 설명한 상황에서는 Server-side Rendering 을 사용할 수 있다. 조금 느릴 순 있지만 페이지는 항상 최신의 상태를 유지한다. 아니면 pre-rendering을 생략하고 client-side JavaScript를 통해 보여줄 수도 있다.

 

Static Generation

Static Generation은 데이터를 포함해도 되고, 포함하지 않아도 수행이 된다.

 

지금까지는 아직 외부의 데이터를 fetching에서 사용하지 않았다. 이러한 페이지들은 앱이 빌드될 때 자동적으로 정적 생성될 것이다.

 

 

그러나 몇몇의 페이지들에서 외부에서 데이터를 fetching 해오지 않으면, 렌더링이 안되는 경우가 있다. 예를 들어 파일 시스템에 접근하거나, 외부 API를 사용하거나, 데이터베이스에 쿼리를 날릴 때이다. Next.js는 이러한 경우(데이터를 사용한 정적 생성)도 지원하고 있다.

 

 

getStaticProps

위의 과정을 가능하게 하는 것이 getStaticProps이다. Next.js 프로젝트에서 한 페이지 컴포넌트를 export할 때, getStaticProps함수를 async형태로 만들어 같이 export해준다. getStaticProps는 빌드 타임에 동작하며 함수 내에서 외부 데이터를 가져와 페이지 컴포넌트에 넘겨줄 수 있다. 기본적인 사용 형태는 다음과 같다.

 

export default function Home(props) { ... }

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

  // props 의 'key' 값은
  // Home 컴포넌트에 전달될 것이다.
  return {
    props: ...
  }
}

 

getStaticProps을 작성하면, Next.js에 이렇게 말할 수 있다. "야, 이 페이지는 어떠한 데이터들에 의존성을 갖고 있으니까 pre-render하기 전에 이것 먼저 해결하고 해!"

 

그럼 이제 getStaticProps를 사용하여 만들어둔 블로그에 포스팅을 해보자.

프로젝트 최상위에 posts폴더를 만든다(이는 pages/posts와는 다른 폴더이다).

다음으로 posts폴더 안에 pre-rendering.md파일과 ssg-ssr.md파일을 만들어주자.

 

// 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 "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

 

// 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: "Can I pre-render this page **ahead** of a user's request?" 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.

 

두 마크다운 파일 상단에는 titledate라는 메타데이터 섹션이 있는 것을 볼 수 있다. 이것은 YAML Front Matter라고 불리우며, gray-matter라는 라이브러리를 사용하여 파싱될 것이다.

 

그리고 이제 index.js페이지를 수정해야한다.

각 마크다운 파일에서 title, date와 파일 명을 가져온다.

해당 데이터들을 index.js페이지에서 date를 기준으로 정렬시킨다.

 

 

getStaticProps 구현

먼저, 마크다운 파일의 메타데이터를 파싱하기 위해 gray-matter를 설치해주자.

 

npm install gray-matter

 

다음으로 파일 시스템에서 데이터를 가져오기 위해 간단한 라이브러리를 만들어보자.

프로젝트 최상위에 lib폴더를 만든다. lib안에 posts.js파일을 만들어 다음과 같이 작성한다.

 

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 => {
    // 파일 명에서 ".md" 확장자를 제거한다.
    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) => {
    if (a.date < b.date) {
      return 1
    } else {
      return -1
    }
  })
}

 

이제 index.js에서 getStaticProps를 작성하고 그 안에서 getSortedPostsData를 import해주어야 한다.

 

index.js에서 다음 코드를 Home컴포넌트 위에 작성해주자.

 

import { getSortedPostsData } from '../lib/posts'

export async function getStaticProps() {
  const allPostsData = getSortedPostsData()
  return {
    props: {
      allPostsData
    }
  }
}

 

getStaticProps에서 리턴하는 props객체의 allPostsDataHome컴포넌트에 prop으로 전달될 것이다. Home에서는 다음과 같이 allPostsData에 접근할 수 있다.

 

export default function Home ({ allPostsData }) { ... }

 

블로그 포스트를 화면에 나타내기 위해, Home컴포넌트를 조금 수정해주자.

 

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      {/* 기존의 코드는 그대로. */}

      {/* 아래 <section> 태그로 감싸진 부분을 추가해준다. */}
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  )
}

 

여기까지 했다면 다음과 같은 결과를 볼 수 있다.

 

 

아래는 우리가 코드로 작성한 과정을 정리한 그림이다. 파일 시스템에서 데이터(포스트)를 가져와 인덱스 페이지 리스트를 렌더링 했다.

 

 

 

'Next.js' 카테고리의 다른 글

[Next.js] Dynamic Routes  (0) 2021.04.10
[Next.js] Assets, Metadata와 CSS  (1) 2021.04.07
[Next.js] 페이지 간 이동하기  (0) 2021.04.06
[Next.js] Next.js 프로젝트 시작하기  (0) 2021.04.06

생강강

,