개발/React

[Next.js] Next js를 배워보자 #5 - Pre-rendering과 Data Fetching

lanace 2020. 9. 10. 16:46

이번장에선 외부 데이터를 어떻게 fetching하는지 살펴본다.

이번 장에서는

이번 장에선 아래와 같은 내용을 살펴본다.

  • Next.js 의 pre-rendering 피쳐를 살펴본다.
  • pre-rendering의 두가지를 살펴본다.
    • Static Generation
    • Server-side Rendering
  • 데이터 유무에 따른 static Generation방식을 살펴본다
  • [getStaticProps](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) 와 사용법에 대해 알아본다.
  • [getStaticProps](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation)의 유용한 팁을 알아본다.

Pre-rendering


Data fetching에 대해 알아보기 전에, Next.js의 가장 중요한 컨셉중에 하나인 Pre-rendering 에 대해 살펴보자

기본적으로, Next.js의 모든 페이지는 pre-rendering 되어진다. 이것은 Next.js가 각각의 HTML 페이지를 생성한다는것을 의미한다. Client-side Rendering 에서 Javascript가 그려주는것 대신에...

Pre-rendering은 더 좋은 성능과 SEO를 자랑한다.

각각의 생성된 HTML은 각 페이지에 꼭 필요로하는 최소한의 Javascript 코드로 이루어져있다.

브라우저에서 페이지가 로드될때 Javascript 코드가 실행되고, 인터렉티브를 생성한다.

javascript 코드가 동작하면서 인터렉션을 생성하는 과정을 hydration 이라 한다.

Pre-rendering이 발생하는 과정


다음과 같은 과정으로 pre-rendering이 이루어진다.

  • 브라우저에서 Javascript 가 Disable 된다.
  • 페이지에 접근을 시도한다.

아마 Javascript 없이 랜더링 되는것을 볼 수 있을것이다. Next.js가 pre-rendering 된 정적 HTML파일을 가지고 있기 때문이다.

Javascript를 disable시키면 CSS도 같이 disabled 되어서 스타일을 볼 수 없다.

순수 React.js 앱이라면 pre-rendering 되지 않는다. 그래서 Javascript를 disable시키면 아무것도 볼 수 없을것이다.

요약: Pre-rendering vs No Pre-rendering

아래 그림을 살펴보자.

https://nextjs.org/static/images/learn/data-fetching/pre-rendering.pnghttps://nextjs.org/static/images/learn/data-fetching/no-pre-rendering.png

다음으로 pre-rendering 의 두가지 형태를 살펴보자.

Pre-rendering의 두가지 형태


Next.js는 Static Generation 과 Server-side Rendering 이 있다.

차이점은 언제 HTML 페이지를 생성하느냐이다.

  • Static Generation은 빌드할때 HTML 페이지를 생성한다.
    Pre-rendering 된 HTML은 각 요청마다 재사용된다.
  • Server-side 랜더링은 각각의 요청이 왔을때 HTML을 생성한다.

https://nextjs.org/static/images/learn/data-fetching/static-generation.pnghttps://nextjs.org/static/images/learn/data-fetching/server-side-rendering.png

개발모드에선 모든 페이지가 각각의 요청에 대해서 pre-rendering 된다.

Per-page Basis

Next.js에서는 각각의 페이지에 두가지중 하나를 선택할 수 있다.

https://nextjs.org/static/images/learn/data-fetching/per-page-basis.png

각각의 페이지에서 어떤 rendering 방식을 사용할지 결정할 수 있다.

언제 Static Genderation과 Server-side Rendering 을 사용하는가?

결론부터 말하자면 Static Generation을 추천한다. 왜냐면 애플리케이션이 빌드되고 CDN으로 배포되면 매 요청마다 server-side Rendering 보다 빠르고 쉽기 때문이다.

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

Static Generation 의 예로는 다음과 같은것들이 있다.

  • 마케팅 페이지
  • 블로그 포스트
  • 이커머스 상품 리스트
  • 도움말 또는 문서 페이지

스스로에게 물어보아라. 사용자가 요청하기 전에 페이지를 생성해두어도 되는지...

만약 그렇다면 Static generation을 사용하자.

반면에 만약 사용자의 요청 이전에 HTML문서를 만드는게 좋지 않다면 Static Generation은 좋은 생각이 아니다. 아마도 데이터가 업데이트되거나, 컨텐츠가 매 요청마다 달라지는 경우가 이 경우일 것이다.

이러한 경우엔, Server-side Rendering 이 낫다. 이 방법은 조금 느리지만 항상 최신의 데이터를 사용하여 보여준다. 또한 pre-rendering과 client-side에서 데이터를 갱신하는 경우를 생략할 수 있다.

데이터 유무에 따른 Static Generation


Static Generation은 외부 Data 를 사용할수도 있고, 사용하지 않을수도 있다.

어느정도, 생성한 모든 페이지가 외부 데이터를 fetching하려고 하진 않는다. 몇몇 페이지는 자동으로 앱이 빌드될때 정적으로 생성되어진다.

https://nextjs.org/static/images/learn/data-fetching/static-generation-without-data.png

하지만 몇몇 페이지에선 외부에서 데이터를 불러와서 HTML 렌더링을 할지도 모른다.

예를들면 파일 시스템이나 외부 API나 데이터베이스 등이 있을것이다.

https://nextjs.org/static/images/learn/data-fetching/static-generation-with-data.png

getStaticProps를 사용한 Data fetching

어떻게 동작하는가? Next.jsdptjsms 페이지 컴포넌트를 export 할때와 async function이 getStaticProps를 호출할때 동작한다.

  • getStaticProps는 production 모드에서 빌드타임에 동작한다.
  • function 내부에선 외부 데이터를 fetch하고 props를 페이지에 보낼 수 있다.
export default function Home(props) { ... }

export async function getStaticProps() {
  // Get external data from the file system, API, DB, etc.
  const data = ...

  // The value of the `props` key will be
  //  passed to the `Home` component
  return {
    props: ...
  }
}

근본적으로 getStaticProps는 Next.js에게 이 페이지는 데이터 의존성이 있으니 pre-render 할때 데이터를 먼저 가져와주도록 한다.

개발모드에선 getStaticProps 는 각각의 요청에 따라 동작한다.

Blog Data


파일 시스템을 사용해 블로그 데이터를 추가해보자.

각각의 블로그 포스팅들은 Markdown 파일로 되어있다.

  • Create a new top-level directory called posts (this is not the same as pages/posts).
  • Inside, create two files: pre-rendering.md and ssg-ssr.md.
  • posts 라는 디렉토리를 추가한다. (/pages/posts)
  • 내부에 파일을 생성한다.

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.

각각의 Markdown 파일은 상단에 title과 date라는 메타데이터를 추가했다.
이 형태는 YAML Front Matter라고 불리며, gray-matter 라는 라이브러리가 사용하는 형태이다.

getStaticProps에서 블로그 데이터 파싱

Now, let’s update our index page (pages/index.js) using this data. We’d like to:

이제 pages/index.js 파일을 수정해보자.

  • 각각의 markdown 파일의 title과 data, name을 파싱한다.
  • 인덱스 페이지에 날짜순으로 정렬된 데이터를 리스팅한다.
  •  

pre-render를 위해 getStaticProps가 중요하다.

https://nextjs.org/static/images/learn/data-fetching/index-page.png

Implement getStaticProps

먼저 gray-matter 라는 라이브러리를 설치한다.

npm install gray-matter

Next, we’ll create a simple library for fetching data from the file system.

다음으로 파일 시스템에서 데이터를 패치해오는 간단한 라이브러리를 생성할 것이다.

  • 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() {
  // Get file names under /posts
  const fileNames = fs.readdirSync(postsDirectory)
  const allPostsData = fileNames.map(fileName => {
    // Remove ".md" from file name to get id
    const id = fileName.replace(/\.md$/, '')

    // Read markdown file as string
    const fullPath = path.join(postsDirectory, fileName)
    const fileContents = fs.readFileSync(fullPath, 'utf8')

    // Use gray-matter to parse the post metadata section
    const matterResult = matter(fileContents)

    // Combine the data with the id
    return {
      id,
      ...matterResult.data
    }
  })
  // Sort posts by date
  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1
    } else {
      return -1
    }
  })
}

그리고 pages/index.js에서 import 해준다.

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

Then call this function in [getStaticProps](https://nextjs.org/learn/basics/data-fetching/docs/basic-features/data-fetching#getstaticprops-static-generation). You need to return the result inside the props key:

그다음 getStaticProps 에서 getSortedPostData 함수를 호출해준다. 내부에선 props를 반환해준다.

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

Once this is set up, the allPostsData prop will be passed to the Home component. To see this, modify the component definition to take { allPostsData }:

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

데이터를 보여주기 위해서 다른

태그를 추가한다.
export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>…</Head>
      <section className={utilStyles.headingMd}>…</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>)
}

이제 Post 정보에 접근할 수 있게 되었다.

https://nextjs.org/static/images/learn/data-fetching/blog-data.png

ㅊㅋㅊㅋ! 외부 데이터 fetching에 성공했고, pre-rendering된 인덱스 페이지를 얻었다.

https://nextjs.org/static/images/learn/data-fetching/index-page.png

세부적인 getStaticProps


이제 getStaticProps를 사용하기 위한 필수적인 내용을 살펴보자

외부 API나 Database Query를 사용한 Fetching

lib/posts.js에서 파일 시스템에서 데이터를 fetching하는 getSortedPostsData를 구현했다.

하지만 외부 API같은 다른곳에서 데이터를 가져올 순 없다.

import fetch from 'node-fetch'

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from an external API endpoint
  const res = await fetch('..')
  return res.json()
}

또한 직접적으로 쿼리를 날릴수도 없다.

import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from a database
  return databaseClient.query('SELECT posts...')
}

이러한 것들을 가능하게 해주는것이 getStaticProps인데, getStaticProps는 server-side에서만 동작하기 때문이다. client-side에선 돌아가지 않는다. 또한 JS 번들러도 포함되지 않는다.

즉, 브라우저가 보내는 요청 없이 직접적으로 디비에 쿼리를 날린다는 것이다.

Development vs. Production

  • 개발모드에선 매 요청마다 실행된다.
  • 프로덕션 모드에선 getStaticProps는 빌드타임에 실행된다.

빌드타임에 동작되기 때문에 요청한 시간에 데이터는 사용할 수 없다. 예를들면 query params나 HTTP Header같은 것을은 사용할 수 없다는 것이다.

Page 에서만 허용

페이지는 getStaticProps를 export 할 수 있따. page가 아닌 파일에서는 export 할 수 없다.

이렇게 제한하는 이유중에 하나는 React는 모든 페이지가 rendering되기 전에 데이터를 요구하기 때문이다.

요청시간에 Data fetching이 필요하다면?

사용자의 요청 이전에 pre-render 할 수 없다면 static generation은 좋은 생각이 아니다.

페이지가 종종 업데이트 되거나 페이지가 매 요청마다 바뀌는 경우가 그러한 케이스다.

이러한경우, Server-side 랜더링이나 pre-rendering 을 스킵하는것이 더 좋은 선택이다.

그러한 전략에 대해서 알아보자.

요청 시간에 Data fetching하기


만약 빌드타임에 데이터를 불러오는게 아니라 요청 시간에 데이터를 불러오길 원한다면 Server-side Rendering 을 해야한다.

https://nextjs.org/static/images/learn/data-fetching/server-side-rendering-with-data.png

Server-side Rendering을 위해서 Page에서 getServerSideProps를 export 해야한다.

getServerSideProps의 사용

아래 코드는 getServerSideProps를 사용하기 위한 기본 코드이다.

위에서 했던 블로그 예제에선 필요하지 않다. 그래서 따로 구현하지 않았었다.

export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    }
  }
}

getServerSideProps는 요청시간에 호출되기 때문에 context라는 parameter는 request 에 대한 인자를 포함하고 있다.

You should use [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) only if you need to pre-render a page whose data must be fetched at request time. Time to first byte (TTFB) will be slower than [getStaticProps](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) because the server must compute the result on every request, and the result cannot be cached by a CDN without extra configuration.

매 요청시간마다 데이터를 fetch하는 페이지에서 pre-render 할때 getServerSideProps를 사용하면 된다.

TTFB (Time to First byte)는 getStaticProps보다 느리다. 왜냐하면 서버에서 매 요청마다의 결과값을 기다려야 하기 때문이다. 그리고 결과는 CDN에 의해서 캐시될 수 없다. (데이터가 바뀌기 때문...)

Client-side Rendering

데이터를 Pre-render 할 필요가 없다면 다음과 같은 Client-side rendering 을 사용하자

  • 외부 데이터를 요구하지 않는 페이지는 정적으로 생성해라
  • 페이지를 로딩할때 외부 데이터를 클라이언트에서 fetching하자

여기서 말하는 client-side Rendering은 부분적으로 나중에 가져와도 괜찮은 부분들을 얘기하는듯 하다.

https://nextjs.org/static/images/learn/data-fetching/client-side-rendering.png

이러한 접근법은 사용자 대시보드 페이지에 적합하다. 왜냐하면 대시보드는 private하고, 사용자별 페이지를 가지고 있으며 SEO와 연관되어있으며, pre-rendering된 페이지를 요구하지도 않는다.

데이터를 요구할때마다 데이터가 종종 업데이트 된다.

SWR

SWR은 Data를 fetching 하는 React hook 기반의 라이브러리이다.

client side에서 데이터를 호출할때 매우 추천한다.

캐시나 정합성, 크래킹 등등 많은 장점을 가지고있다.

자세히 다루진 않고, 아래 간단한 예시 코드만 보고 넘어가자

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetch)

  if (error) {
        return (
            <div>failed to load</div>
        )
    }

    if (!data) {
        return <div>loading...</div>
    }

    return <div>hello {data.name}!</div>}
}

끗!

다음장에선 각각의 블로그 글이 동적으로 라우팅 되는지에 대해서 알아볼 것이다.