Next.js 간단한 첫 블로그를 만들어 보자(4): 사전 렌더링과 데이터 가져오기(2)
저번 시간에 이어서 사전 렌더링과 데이터 가져오는 것에 대해서 이야기하겠습니다.
https://nextjs.org/learn/basics/data-fetching/with-data
Learn | Next.js
Production grade React applications that scale. The world’s leading companies use Next.js by Vercel to build pre-rendered applications, static websites, and more.
nextjs.org
nextjs의 첫 앱을 만드는 방법이며 영어로 된 문서를 한글로 작성해보며 작성하고 있습니다. 틀린 부분도 있겠지만.. 지적해주시면 수정해 놓겠습니다~
데이터가 있거나 없는 정적 생성하기
정적 생성은 데이터 유무에 관계없이 수행할 수 있습니다.
지금까지 우리가 만든 모든 페이지는 외부 데이터를 가져올 필요가 없습니다. 이러한 페이지는 앱이 프로덕션용으로 빌드될 때 자동으로 정적으로 생성됩니다.
그러나 일부 페이지의 경우 먼저 외부 데이터를 가져오지 않고는 HTML을 렌더링하지 못할 수 있습니다. 파일 시스템에 액세스하거나 외부 API를 가져오거나 빌드 시 데이터베이스를 쿼리해야 할 수도 있습니다. Next.js는 이 경우( 데이터를 사용한 정적 생성 )를 즉시 지원합니다 .
getStaticProps를 사용하여 데이터로 정적 생성하기
어떻게 작동할까요? Next.js에서 페이지 구성 요소를 내보낼 때 getStaticProps라는 비동기 함수로 내보낼 수 있습니다. 아래와 같이 getStaticProps는 프로덕션에서 빌드 시 실행되고 함수 내에서 외부 데이터를 가져와 페이지에 소품으로 보낼 수 있습니다.
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에 다음과 같이 알릴 수 있습니다. "이 페이지에는 일부 데이터 종속성이 있습니다. 따라서 빌드 시 이 페이지를 미리 렌더링할 때 먼저 문제를 해결해야 합니다!"
영어로는 Hey, this page has some data dependencies — so when you pre-render this page at build time, make sure to resolve them first!”
getStaticProps를 사용해보자!
해보면 배우기가 더 쉽기 때문에 다음 페이지부터 getStaticProps를 사용하여 블로그를 구현하겠습니다.
블로그 데이터 추가하기
이제 파일 시스템을 사용하여 앱에 블로그 데이터를 추가합니다. 각 블로그 게시물은 마크다운 파일이 됩니다.
- 최상위 폴더에 posts 폴더를 만듭니다(pages/posts와 동일하지 않음).
- posts 폴더 안에 pre-rendering.md 및 ssg-ssr.md의 두 파일을 만듭니다.
이제 다음 코드를 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.
다음 코드를 posts/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.
getStaticProps에서 블로그 데이터 구문 분석하기
이제 이 데이터를 사용하여 인덱스 페이지(pages/index.js)를 다음과 같이 업데이트하겠습니다.
- 각 마크다운 파일을 구문 분석하고 title, date 및 file name(게시물 URL의 ID로 사용됨)을 가져옵니다.
- 인덱스 페이지에 날짜별로 정렬된 데이터를 나열합니다.
사전 렌더링에서 이를 수행하려면 getStaticProps를 구현해야 합니다.
getStaticProps 구현
먼저 각 마크다운 파일의 메타데이터를 구문 분석할 수 있는 gray-matter을 설치 합니다.
찾아보니 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() {
// 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(({ date: a }, { date: b }) => {
if (a < b) {
return 1
} else if (a > b) {
return -1
} else {
return 0
}
})
}
이제 getSortedPostsData에 대한 가져오기를 추가하고 pages/index.js의 getStaticProps 내에서 호출해야 합니다.
pages/index.js를 열고 내보낸 home component 위에 다음 코드를 추가합니다.
import { getSortedPostsData } from '../lib/posts'
export async function getStaticProps() {
const allPostsData = getSortedPostsData()
return {
props: {
allPostsData
}
}
}
getStaticProps의 props 객체 내부에 allPostsData를 반환하면 블로그 게시물이 Home 구성 요소에 prop으로 전달됩니다. 이제 다음과 같이 블로그 게시물에 액세스할 수 있습니다.
export default function Home ({ allPostsData }) { ... }
블로그 게시물을 표시하기 위해 home component를 업데이트하여 자기 소개가 있는 섹션 아래에 데이터가 포함된 다른 <section> 태그를 추가해 보겠습니다. props를 ()에서 ({ allPostsData })로 변경하는 것도 잊지 마세요!
export default function Home({ allPostsData }) {
return (
<Layout home>
{/* Keep the existing code here */}
{/* Add this <section> tag below the existing <section> tag */}
<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 사용에 대한 몇 가지 팁에 대해 이야기해 보겠습니다.
getStaticProps 세부 정보
다음은 알아야 할 몇 가지 필수 정보 getStaticProps입니다.
외부 API 또는 쿼리 데이터베이스 가져오기
lib/posts.js에서 파일 시스템에서 데이터를 가져오는 getSortedPostsData를 구현했습니다. 그러나 외부 API 엔드포인트와 같은 다른 소스에서 데이터를 가져올 수 있으며 잘 작동합니다.
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가 server-side에서만 실행되기 때문에 가능합니다. getStaticProps는 client-side에서 실행되지 않습니다. 브라우저용 JS 번들에도 포함되지 않습니다. 즉, 직접 데이터베이스 쿼리와 같은 코드를 브라우저로 보내지 않고도 작성할 수 있습니다.
Development vs. Production
- Development(npm run dev 또는 yarn dev)에서 getStaticProps는 모든 요청에 대해 실행됩니다.
- Production에서 getStaticProps는 빌드 시 실행됩니다. 그러나 이 동작은 getStaticPaths에서 반환된 대체 키를 사용하여 향상될 수 있습니다.
빌드 시간에 실행되도록 되어 있기 때문에 쿼리 파라미터나 HTTP 헤더와 같이 요청 시간에만 사용할 수 있는 데이터는 사용할 수 없습니다.
페이지에서만 허용
getStaticProps는 페이지에서만 내보낼 수 있습니다. 페이지가 아닌 파일에서는 내보낼 수 없습니다.
이러한 이유 중 하나는 페이지가 렌더링되기 전에 React에 필요한 모든 데이터가 있어야 하기 때문입니다.
요청 시 데이터를 가져와야 하는 경우 어떻게 해야하나요?
Static Generation은 사용자의 요청에 앞서 페이지를 미리 렌더링할 수 없는 경우 사용하기 부적절합니다. 페이지에 자주 업데이트되는 데이터가 표시되고 모든 요청에 따라 페이지 콘텐츠가 변경될 수 있습니다.
이와 같은 경우 Server-side Rendering을 시도 하거나 사전 렌더링을 건너뛸 수 있습니다.
요청 시 데이터 가져오기
빌드 시간이 아닌 요청 시간 에 데이터를 가져와야 하는 경우 Server-side Rendering을 시도할 수 있습니다 .
Server-side Rendering을 사용하려면 페이지에서 getStaticProps 대신 getServerSideProps를 내보내야 합니다.
getServerSideProps 사용
다음은 getServerSideProps의 시작 코드입니다. 블로그 예제에서는 필요하지 않으므로 구현하지 않습니다.
나중에 검색과 같은 경우에 사용하기 좋을 것입니다.
export async function getServerSideProps(context) {
return {
props: {
// props for your component
}
}
}
getServerSideProps는 요청 시 호출되므로 해당 파라미터(context)에는 요청 특정 파라미터가 포함됩니다.
요청 시 데이터를 가져와야 하는 페이지를 미리 렌더링해야 하는 경우에만 getServerSideProps를 사용해야 합니다. 서버가 모든 요청에 대해 결과를 계산해야 하고 추가 구성 없이 CDN에서 결과를 캐시할 수 없기 때문에 TTFB(Time to First byte)는 getStaticProps보다 느립니다.
Client-side Rendering
데이터를 미리 렌더링할 필요가 없는 경우 다음 전략(Client-side Rendering이라고 함)을 사용할 수도 있습니다.
- 외부 데이터가 필요하지 않은 페이지 부분을 정적으로 생성(사전 렌더링)합니다.
- 페이지가 로드되면 JavaScript를 사용하여 클라이언트에서 외부 데이터를 가져오고 나머지 부분을 채웁니다.
이 접근 방식은 예를 들어 사용자 대시보드 페이지에 적합합니다. 대시보드는 사용자별 비공개 페이지이기 때문에 SEO는 관련이 없으며 페이지를 미리 렌더링할 필요가 없습니다. 데이터는 자주 업데이트되므로 요청 시 데이터를 가져와야 합니다.
SWR
Next.js 뒤에 있는 팀은 SWR이라는 데이터 가져오기를 위한 React 후크를 만들었습니다. 클라이언트 측에서 데이터를 가져오는 경우 적극 권장합니다. 캐싱, 재검증, 초점 추적, 간격에 따라 다시 가져오기 등을 처리합니다. 여기에서 세부 사항을 다루지는 않겠지만 다음은 사용 예입니다.
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 문서를 확인하세요.
이상 사전 렌더링과 데이터 가져오기를 마무리 했습니다.!
다음시간에는 dynamic routes에 대해 배워보겠습니다!