[번역] Next.js 시작하기 4. 사전 렌더링과 데이터 가져오기

여름노래불러줘·2020년 5월 11일
5
post-thumbnail

이번 레슨에서 배울 것들

블로그를 만들고 싶습니다. (여기 원하는 결과가 있습니다) 그러나 지금까지 블로그에 아무 내용도 추가하지 않았습니다. 이 레슨에서 블로그 외부의 데이터를 앱으로 가져오는 방법을 알아볼 것입니다. 우린 블로그 컨텐츠를 파일 시스템에 저장할 것입니다. 그러나 다른 곳에 컨텐츠가 저장되어도 잘 작동할 것입니다. (데이터베이스나 헤드가 없는 CMS)

Next.js 사전 렌더링 기능.

두 가지 형태의 사전 렌더링: 정적 생성과 서버-사이드 렌더링.

  • 데이터 유무에 따른 정적 생성.
  • 'getStaticProps' 및 이를 사용해 외부 블로그 데이터를 인덱스 페이지로 가져오는 방법.
  • 'getSaticProps' 에 대한 유용한 정보들.

스타터 코드 다운로드(선택적)

이전 레슨부터 계속하고 있다면, 이 페이지를 건너뛰어도 됩니다. 다음으로 이동하세요.

이전 레슨부터 계속하고 있지 않다면, 아래의 스타터 코드를 다운로드 설치 및 실행할 수 있습니다. 이전 레슨의 결과와 동일하게 'Next-js-blog' 디렉토리를 설정합니다.

이전 레슨을 완료했다면 이 작업은 필요하지 않습니다.

npm init next-app nextjs-blog --example "https://github.com/zeit/next-learn-starter/tree/master/data-fetching-starter"

그리고 커맨드 출력으로부터의 명령을 따라하세요. ('cd'로 디렉토리로 이동한 다음, 개발 서버를 시작하세요)

또한 다음의 파일들을 업데이트 해야합니다.

  • 'public/images/profile.jpg' 의 이름을 가진 당신의 사진 (추천 해상도: 400px 폭/높이)
  • 'components/layout.js' 안의 변수 'const name = '[Your Name]' 에 당신의 이름.
  • 'pages/index.js' 안의 태그 '<p>[Your Self Introduction]</p> 에 당신의 자기소개.

사전 렌더링

데이터 가져오기에 대해 이야기 하기 전에 Next.js 의 가장 중요한 개념 중 하나인 사전 렌더링에 대해 이야기 해봅시다.

기본적으로 Next.js 는 모든 페이지를 사전-렌더링 합니다. 이는 클라이언트 사이드 자바스크립트로 모든 작업을 하는 대신, Next.js 가 각 페이지들을 위해 HTML을 미리 생성한다는 것을 말합니다. 사전 렌더링 결과로 더 나은 성능과 SEO(검색엔진 최적화)를 얻을 수 있습니다.

생성된 각 HTML은 페이지들에 필요한 최소한의 자바스크립트 코드와 연결됩니다. 페이지가 브라우저에 의해 로드될 때 자바스크립트가 실행되고 완벽히 상호작용 가능한 페이지를 만들어냅니다. (이 작업을 hydration 이라고 부릅니다.)

사전 렌더링이 이루어지는지 확인하기

아래 단계를 따라하면 사전-렌더링이 이루어지는지 확인할 수 있습니다.

앱이 자바스크립트 없이 렌더링 된 것을 볼 수 있었습니다. Next.js가 앱을 정적 HTML로 사전 렌더링해 자바스크립트 실행 없이도 앱 UI를 볼 수 있게 해줍니다.

노트: localhost 에서도 위의 단계들을 시도해볼 수 있지만 자바스크립트를 비활성화시키면 CSS는 로드되지 않을 것입니다.

앱이 일반 React.js 면 (Next.js 없는) 사전 렌더링이 없습니다. 그래서 자바스크립트를 비활성화 하면 앱을 볼 수 없을 것입니다. 예를 들면:

  • 브라우저에서 자바스크립트를 활성화하고 이 페이지를 확인하십시오. 이 페이지는 Creat React App 으로 빌드된 일반 React.js 앱입니다.
  • 이제 자바스크립트를 비활성화하고 같은 페이지에 다시 접근해보세요..
  • 앱을 더이상 볼 수 없을 것입니다 — 대신, "이 앱을 실행하려면 자바스크립트를 활성화시켜야 합니다." 라고 할 것입니다. 앱이 정적 HTML로 사전 렌더링되지 않았기 때문 입니다.

요약: 사전 렌더링 vs 사전 렌더링 없음

빠른 그래픽 요약:

다음, Next.js에서 사전-렌더링의 두 가지 형태에 대해 알아 봅시다.

사전 렌더링의 두 가지 형태

Next.js는 두 가지 형태의 사전 렌더링을 가지고 있습니다: 정적 생성서버 사이드 렌더링 입니다. 페이지를 위한 HTML 을 생성할 때 차이점이 있습니다.

  • 정적 생성빌드 시 HTML을 생성하는 사전 렌더링 방법입니다. 그런 다음 사전 렌더링 된 HTML은 각 요청에서 재사용됩니다.
  • 서버 사이드 렌더링각 요청마다 HTML을 생성하는 사전 렌더링 방법입니다.

개발 모드에서('npm run dev' 나 'yarn dev'로 앱을 실행할 때)는, 모든 페이지가 각 요청마다 사전 렌더링 됩니다. — 정적 생성을 사용하는 페이지더라도 말이죠.

페이지 기준

중요한 것은, Next.js가 각 페이지를 위해 어떤 사전-렌더링 형태를 사용할 것인지 선택하게 해줍니다. 대부분의 페이지에 정적 생성을 사용하고 나머지 페이지에 서버-사이드 렌더링을 사용하는 것으로 "하이브리드" Next.js 앱을 만들 수 있습니다.

언제 정적 생성을 사용하고 언제 서버 사이드 렌더링을 사용하는가?

가능하면 정적 생성(데이터 유무에 관게 없이)을 사용하는 것을 추천합니다.
CDN 서버에서 페이지를 한 번 빌드하고 제공 할 수 있기 때문에, 매 요청마다 서버 사이드 렌더링을 사용하는 것 보다 훨씬 빠릅니다.

많은 유형의 페이지에 정적 생성을 사용할 수 있습니다. 얘네들을 포함해서요:

  • 마케팅 페이지
  • 블로그 포스트
  • 전자 상거래 제품 목록
  • 도움말 및 도큐먼테이션

"사용자의 요청 전에 페이지를 미리 렌더링 할 수 잇을까?" 를 스스로에게 물어봐야 합니다. 답이 "그렇다" 라면 정적 생성을 사용해야 합니다.

반면, 사용자의 요청 전에 미리 페이지를 렌더링 할 수 없다면 정적 생성은 좋은 생각이 아닙니다. 아마도 페이지가 자주 업데이트 된 데이터를 보여주고, 매 요청마다 페이지의 내용이 바뀔 수 있습니다.

이런 경우, 서버-사이드 렌더링을 사용할 수 있습니다. 이는 더 느리지만, 사전 렌더링 된 페이지는 언제나 최신 상태일 것입니다. 혹은 사전 렌더링을 건너뛰고 클라이언트 사이드 측 자바스크립트를 사용해 데이터를 채울 수 있습니다.

우린 정적 생성에 집중할 것입니다.

이 레슨에서, 우리는 정적 생성에 집중할 것입니다. 다음 페이지에서, 데이터 유무에 관계없이 정적 생성에 대해 이야기 하겠습니다.

데이터 유무에 따른 정적 생성

정적 생성은 데이터 유무에 관계 없이 완료될 수 있습니다.
지금까지, 우리가 만든 모든 페이지들은 외부 데이터를 요구하지 않았습니다.
이런 페이지들은 자동으로 앱이 프로덕션용으로 빌드될 때, 정적으로 생성됩니다.

그러나, 일부 페이지들은 첫 외부 데이터 가져오기 없이 HTML 을 렌더링 하지 못할 수 있습니다. 파일 시스템에 접근하거나, 외부 API 가져오기, 빌드 시 데이터베이스에 쿼리를 날려야 할수도 있습니다. Next.js 는 이런 경우를 지원 합니다. — 별도 설정 없이 — 데이터와 함께 정적 생성.

getStaticProps 를 사용한 정적 생성과 데이터

어떻게 작동하나요? Next.js 에서 페이지 컴포넌트를 내보낼 때, 'getStaticProps' 라는 'async' 함수 또한 내보낼 수 있습니다. 이러면...:

  • 'getStaticProps' 가 프로덕션에서 빌드 시 실행됩니다. 그리고...
  • 함수 안에, 외부 데이터를 가져오고 페이지의 props 로 전달할 수 있습니다.
export default function Home(props) { ... }

export async function getStaticProps() {
  // 파일 시스템이나 API, DB 등에서 외부 데이터 얻기
  const data = ...

  // `props` 키의 값이
  // `Home` 컴포넌트에 전달 될 것 
   
  return {
    props: ...
  }
}

본질적으로 'getStaticProps' 는 Next.js 에게 "이 페이지는 데이터 종속성을 가지고 있으니, 빌드 시 이 페이지를 사전 렌더링 할 때, 데이터부터 먼저 해결해" 라고 말하는 것을 허용합니다.

노트: 개발모드에서, 'getStaticProps' 는 각요청마다 실행됩니다.

* 'getStaticProps' 를 써봅시다.

해보면서 배우기가 더 쉽습니다, 그러니 다음 페이지부터 시작 해보죠. 블로그를 구현하는데 'getStaticProps' 를 사용할 것입니다.

6. 블로그 데이터

파일시스템을 사용해, 블로그 앱에 데이터를 추가할 것입니다. 각 블로그 포스트는 마크다운 파일이 될 것입니다.

  • 'posts' 라는 최상위 디렉토리를 하나 만드십시오. ('pages/posts' 와 같은 것이 아닙니다.)
    -그 안에 'pre-rendering.md' 와 'ssg-ssr.md' 두 개의 파일을 만드십시오.

'pre-rendering.md' 에 다음을 복사하십시오.

---
title: '사전 렌더링의 두 가지 형태'
date: '2020-01-01'
---

중요한 것은,  Next.js가 각 페이지를 위해 어떤 사전-렌더링 형태를 사용할 것인지 **선택하게** 해줍니다.  대부분의 페이지에 정적 생성을 사용하고 나머지 페이지에 서버-사이드 렌더링을 사용하는 것으로  "하이브리드" Next.js 앱을 만들 수 있습니다. 

'ssg-ssr.md' 에 다음을 복사하십시오.

---
title: '언제 정적 생성을 사용하고, 언제 서버 사이드 렌더링을 사용하는가'
date: '2020-01-02'
---

가능하면 정적 생성(데이터 유무에 관게 없이)을 사용하는 것을 추천합니다. 
CDN 서버에서 페이지를 한 번 빌드하고 제공 할 수 있기 때문에, 매 요청마다 서버-사이드 렌더링을 사용하는 것 보다 훨씬 빠릅니다. 

많은 유형의 페이지에 정적 생성을 사용할 수 있습니다. 얘네들을 포함해서요:

- 마케팅 페이지
- 블로그 포스트
- 전자 상거래 제품 목록
- 도움말 및 도큐먼테이션

"사용자의 요청 **전에** 페이지를 미리 렌더링 할 수 잇을까?"를 스스로에게 물어봐야 합니다. 답이 "그렇다" 라면 정적 생성을 사용해야 합니다. 

반면, 유저의 요청 전에 미리 페이지를 렌더링 할 수 없다면, 정적 생성은 좋은 생각이 **아닙니다**. 아마도 페이지가 자주 업데이트 된 데이터를 보여주고, 매 요청마다 페이지의 내용이 바뀔 수 있습니다. 

이런 경우, **서버-사이드 렌더링**을 사용할 수 있습니다. 이는 더 느리지만, 사전-렌더링 된 페이지는 언제나 최신 상태일 것입니다. 혹은 사전-렌더링을 건너뛰고 클라이언트-사이드 측 자바스크립트를 사용해 데이터를 채울 수 있습니다.

아마 각 마크다운 파일이 상단에 'title' 과 'date'의 메타데이터 섹션을 갖는 것을 눈치챘을 것입니다. 이것을 gray-matter 라는 라이브러리를 사용해 파싱될 수 있는 YAML Front Matter 라고 합니다.

7. 'getStaticProps' 에서 블로그 데이터 파싱하기

이제 이 데이터를 사용해 우리의 인덱스 페이지 ('pages/index.js')를 업데이트 해봅시다:

  • 각 마크다운 파일을 파싱해 'title', 'date' 그리고 파일 이름을 가져옵니다 (포스트 URL로 사용 될 'id').
  • 인덱스 페이지에 데이터를 날짜별로 정렬하십시오.

사전-렌더링으로 이것을 하려면, 'getStaticProps' 를 구현해야 합니다.

다음 페이지에서 해보죠!

* getStaticProps 구현

먼저, 각 마크다운 파일 내의 메타데이터를 파싱하게 해주는 gray-matter 를 설치 합니다.

npm install gray-matter

다음, 파일 시스템으로부터 간단한 데이터를 가져올 수 있는 라이브러리를 만들 것입니다.

  • '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" 를 지워 id를 가져옵니다
    const id = fileName.replace(/\.md$/, '')

    // 마크다운파일을 문자열로 읽어들입니다.
    const fullPath = path.join(postsDirectory, fileName)
    const fileContents = fs.readFileSync(fullPath, 'utf8')

    // gray-matter를 이용해 post 메타데이터 섹션을 파싱합니다.
    const matterResult = matter(fileContents)

    // id 와 데이터를 결합합니다.
    return {
      id,
      ...matterResult.data
    }
  })
  // 날짜별로 post를 정렬합니다.
  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1
    } else {
      return -1
    }
  })
}

그리고 'pages/index.js' 에, 이 함수를 가져오십시오.

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

그리고 'getStaticProps' 내에서 이 함수를 호출하십시오. 'props' 키 내부에 결과를 리턴해야 합니다:

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

이게 설정되면, 'allPostsData' prop 'Home' 컴포넌트에 전달 됩니다. 이것을 보기 위해, 컴포넌트의 정의에서 '{ allPostsData }' 를 받게 수정하은십시오.

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

데이터를 표시하기 위해, 컴포넌트의 하단에 다른 '<section>' 태그를 추가하십시오.

export default function Home({ allPostsData }) {
  return (
    <Layout>
      <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>
  )
}

http://localhost:3000 에 접속하면 블로그 데이터가 보여야 합니다!

축하합니다! 성공적으로 외부 데이터를 가져왔고(파일 시스템에서) 이 데이터로 인덱스 페이지를 사전-렌더링 했습니다.

다음 페이지에서 'getStaticProps' 를 사용하는 팁에 대해서 이야기 해봅시다.

8.getStaticProps 세부 사항

우리의 문서에서 'getStaticProps' 에 대한 자세한 정보를 얻을 수있습니다. 그러나 본질적으로 'getStaticProps'에 대해 알아야 할 몇 가지 정보들이 있습니다.

* 외부 API 가져오기나 쿼리 데이터베이스

우리의 'lib/posts.js' 안에 파일시스템으로부터 데이터를 가져오는 'getSortedPostsData'를 구현했습니다. 그러나, 다른 출처로부터 데이터를 가져올 수 있습니다.
외부 API 엔드포인트 처럼 말이죠. 그리고 잘 작동할거에요:

import fetch from 'node-fetch'

export async function getSortedPostsData() {
  // 파일 시스템 대신
  // 외부 API로부터 post 데이터를 가져옵니다.
  const res = await fetch('..')
  return res.json()
}

데이터베이스에 직접 쿼리를 날릴 수도 있습니다:

import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
  // 파일 시스템 대신,
  // 데이터베이스로부터 post 데이터를 가져옵니다.
  return databaseClient.query('SELECT posts...')
}

이것은 'getStaticProps' 가 서버-사이드에서만 작동하기 때문입니다. 절대 클라이언트 사이드에서 작동하지 않습니다. 브라우저의 자바스크립트 번들에도 포함되지 않습니다. 이는 브라우저로 쿼리를 전송하지 않고도 데이터베이스에 쿼리를 날리는 코드를 작성할 수 있음을 말합니다.

* 개발 vs. 프로덕션

  • 개발에서 ('npm run dev' or 'yarn dev'), 'getStaticProps'는 모든 요청에서 실행됩니다.
  • 프로덕션에서, 'getStaticProps' 는 빌드 시 실행됩니다.

* 페이지에서만 허용됨

'getStaticProps' 는 오직 한 페이지로부터 export 될 수 있습니다. 페이지가 아닌 파일로부터는 export 할 수 없습니다.

이런 제한이 걸린 이유중 하나는 React 가 모든 요청된 데이터들이 페이지가 렌더링 되기 전에 필요하기 때문입니다.

* 요청 시 데이터를 가져오는게 필요하다면요?

유저의 요청 전에 미리 페이지를 렌더링 할 수 없다면, 정적 생성은 좋은 생각이 아닙니다. 아마도 페이지가 자주 업데이트 된 데이터를 보여주고, 매 요청마다 페이지의 내용이 바뀔 수 있습니다.

이런 경우, 서버-사이드 렌더링을 사용하거나 사전-렌더링을 건너뛸 수 있습니다. 다음 레슨으로 넘어가기 전에 이런 전략들에 대해 이야기 해 봅시다.

9. 요청 시 데이터 가져오기

빌드시 대신 요청시 데이터 가져오기가 필요하다면, 서버사이드 렌더링을 시도할 수 있습니다:

서버 사이드 렌더링을 사용하기 위해선, 페이지로부터 'getStaticProps' 대신 'getServerSideProps' 를 export 해야 합니다.

* 'getServerSideProps' 사용하기

'getServerSideProps' 를 위한 스타터 코드가 있습니다. 우리의 블로그 예제에는 필요하지 않으니 이것을 구현하지는 않을 겁니다.

export async function getServerSideProps(context) {
  return {![](https://velog.velcdn.com/images%2Fwolverine%2Fpost%2F3153823b-7c5b-4e6a-bbb7-d5fc19931848%2Fclient-side-rendering.png)
    props: {
      // 컴포넌트를 위한 props
    }
  }
}

'getServerSideProps' 는 요청 시 호출되기 때문입니다, 이 파라미터('context')에는 특정 파라미터가 포함됩니다. 우리의 도큐먼테이션에서 이에 대해 더 학습할 수 있습니다.

요청 시 데이터가 반드시 있어야 하는 페이지를 사전-렌더링하는 경우에만 'getServerSideProps' 를 사용해야 합니다.
서버가 매 요청마다 결과를 계산해야하기 때문에, 첫 바이트까지의 시간(TTFB)은 'getStaticProps' 보다 느리고, 특별한 설정 없이 CDN에 의해 결과가 캐시 될 수 없습니다.

* 클라이언트 사이드 렌더링

데이터를 사전-렌더링 할 필요 없으면, 이 전략을 따라할 수 있습니다.(클라이언트 사이드 렌더링이라 불리는):

  • 페이지에서 정적으로 생성(사전-렌더)되는 부분들은 외부 데이터를 요구하지 않습니다.
  • 페이지가 로드 될 때, 클라이언트로부터 자바스크립트를 사용해 외부 데이터를 가져오고 나머지 부분을 채웁니다.

이 접근법은 유저 대시보드 페이지들에서 잘 작동합니다. 예를 들면, 대시보드는 비공개이고, 사용자별 페이지이고, 검색 엔진 최적화와 관련되어 있지 않습니다. 그리고 페이지가 사전-렌더링 되어야 할 필요도 없습니다. 데이터가 자주 업데이트되고, 요청 시 데이터를 가져오는 것을 요구합니다.

SWR

Next.js 팀은 SWR 이라고 불리는 데이터 가져오기를 위한 React 훅을 만들었습니다. 클라이언트 사이드에서 데이터를 가져온다면 SWR를 강력하게 추천합니다. 캐싱, 재확인, 포커스 트래킹, 주기적으로 다시 가져오기 등을 핸들링 할 수 있습니다.
자세한 내용은 여기서 다루지 않겠지만 사용 예시가 하나 있습니다.

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>
}

SWR 도큐먼테이션을 확인해서 더 학습 해보세요.

이게 다 입니다
다음 레슨에서, 우리는 동적 라우트를 이용해 각 블로그 페이지를 만들어 볼 것입니다.

우리의 도큐먼테션에서 'getStaticProps'와 'getServerSideProps' 에 대한 더 자세한 정보를 얻을 수 있습니다.

0개의 댓글