Next.js 프레임워크는 Data Fetching 시에 SSR(Server-side Rendering), SSG(Static-site Generation), 그리고 Dynamic Routing 등의 세 가지 방법을 제공한다. 각각의 방법에 대해서 하나씩 깊게 알아보고 언제 어떻게 써야 하는지 사용법을 공부해 보고자 한다.
SSR(Server-side Rendering)
만약 어떤 페이지에서 getServerSideProps 함수를 호출하면, Next.js는 getServerSideProps에서 데이터를 반환받을 때 마다 이 페이지를 pre-rendering 할 것 이다.
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
여기서 주의할 점. 렌더링 타입에 상관없이, 어떠한 props 든지 페이지 컴포넌트에 전달이 될 것이고 클라이언트 사이드 초기 HTML에 보이게 될 것이다. 이는 페이지가 정확하게 hydrated 되도록 허용한다. 명심해야 하는 점은, props에 민감한 정보를 절대 보내면 안된다.
그렇다면 언제 getServerSideProps 가 실행되는가?
getServerSideProps 는 오직 서버 사이드에서만 실행되고 절대 브라우저에서는 실행되지 않는다. 만약 페이지가 getServerSideProps를 사용한다면,
- 이 페이지를 직접 요청하는 경우, getServerSideProps는 요청 시간에 실행이 되고 이 페이지는 pre-rendered 되어 props를 반환할 것이다.
- 이 페이지를 클라이언트 사이드의 페이지 이동으로 요청하는 경우, Next.js는 API 요청을 서버에 보내고, 이는 getServerSideProps를 실행시킨다.
getServerSideProps는 JSON을 반환하고 이 값은 페이지를 렌더링하는데 사용된다.
언제 getServerSideProps를 사용해야 하는가? 오직 데이터가 요청 시간에만 패치가 되는 페이지를 렌더링 할 때 사용해야 한다. 예를 들면 authorization 헤더나 geolocation 값 같은 것들이 될 수 있다. getServerSideProps를 사용하는 페이지들은 요청한 시간에 서버 사이드 렌더링이 될 것이고 cache-control 헤더가 설정 되었을 때만 캐시될 것이다.
SSR에서 클라이언트 사이드 데이터 패칭 방법은 없을까? 만약 페이지가 빈번하게 업데이트 되는 데이터를 가지고 있고, 데이터를 pre-render 할 필요가 없을 때, 클라이언트 사이드 데이터 패치를 할 수 있다.
- 먼저, 페이지를 데이터 없이 즉시 보여준다. 페이지의 일 부분은 정적 생성(Static Generation)을 통해 pre-render 될 것이다. 빠진 데이터는 로딩 상태를 보여준다.
- 그리고 나서 클라이언트 사이드에서 데이터를 패치하고 준비되면 보여준다.
이러한 접근은 예를 들면 사용자 대시보드 같은 페이지에 잘 동작한다. 왜냐하면 대시보드는 프라이빗하고, 사용자 특정된 페이지이며, SEO가 필요 없고 페이지가 pre-render 될 필요가 없기 때문이다. 데이터가 빈번하게 업데이트 되기 때문에 이는 요청 시간 데이터 패칭을 요구한다.
아래 코드는 요청 시간에 데이터 패치를 어떻게 할 수 있는지 그리고 결과를 어떻게 pre-render 할 수 있는지 코드로 작성한 예시이다.
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
getServerSideProps 안에 Cache-Control 이라는 캐싱 헤더를 넣어서 동적인 응답을 캐싱할 수 있다. 예시는 다음과 같다.
// This value is considered fresh for ten seconds (s-maxage=10).
// If a request is repeated within the next 10 seconds, the previously
// cached value will still be fresh. If the request is repeated before 59 seconds,
// the cached value will be stale but still render (stale-while-revalidate=59).
//
// In the background, a revalidation request will be made to populate the cache
// with a fresh value. If you refresh the page, you will see the new value.
export async function getServerSideProps({ req, res }) {
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
return {
props: {},
}
}
Dynamic Routing
어떤 동적 라우팅(Dynamic Route)을 사용하는 페이지에서 getStaticPaths를 호출하면 Next.js는 getStaticPaths로 특정된 모든 경로를 전부 정적으로 pre-render 한다.
// pages/posts/[id].js
// Generates `/posts/1` and `/posts/2`
export async function getStaticPaths() {
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
fallback: false, // can also be true or 'blocking'
}
}
// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps(context) {
return {
// Passed to the page component as props
props: { post: {} },
}
}
export default function Post({ post }) {
// Render post...
}
getStaticPaths는 언제 사용해야 할까? getStaticPaths는 동적 라우팅을 사용하며 정적으로 pre-render가 일어나야 하는 페이지에서 사용되어야 하며 다음과 같은 조건에서 사용한다.
- 데이터가 headless CMS에서 오는 경우
- 데이터가 데이터베이스에서 오는 경우
- 데이터가 파일 시스템에서 오는 경우
- 데이터가 공개적으로 캐시 되는 경우
- 페이지가 SEO를 위해서 pre-render 되어야 하고 빠르게 나타나져야 하는 경우.
- getStaticProps 는 HTML 과 JSON 파일을 만드는데 둘 다 성능을 위해서 CDN에 의해 캐싱될 수 있다.
getStaticPaths는 프로덕션 빌드 동안에만 실행이 되고, 런타임에는 실행되지 않는다.
getStaticProps 가 getStaticPaths 와 관련해서 실행이 되는 경우는 다음과 같다.
- getStaticProps 는 next build 동안 빌드 중에 반환된 경로들에서 실행된다.
- getStaticProps 는 fallback : true 일 경우 백그라운드에서 실행된다.
- getStaticProps 는 fallback : blocking 일 경우 최초 렌더링 전에 호출된다.
getStaticPaths는 어떤 페이지가 빌드 중에 생성이 될지를 조절할 수 있게 해준다. 빌드 해야 할 페이지가 많으면 빌드가 느려진다. paths에 빈 배열을 넣으면 모든 페이지를 생성하는 작업을 필요에 따라 연기할 수 있다. 이는 Next.js로 여러가지 환경에서 어플리케이션을 배포해야 할 때 도움이 될 수 있다. 예를 들면, 프리뷰에 필요에 맞춰서 모든 페이지 생성을 조절해서 빌드를 빠르게 할 수 있다. 이는 정적인 페이지가 수백, 수천개 이상인 사이트에서 유용하다.
// pages/posts/[id].js
export async function getStaticPaths() {
// When this is true (in preview environments) don't
// prerender any static pages
// (faster builds, but slower initial page load)
if (process.env.SKIP_BUILD_STATIC_GENERATION) {
return {
paths: [],
fallback: 'blocking',
}
}
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to prerender based on posts
// In production environments, prerender all pages
// (slower builds, but faster initial page load)
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// { fallback: false } means other routes should 404
return { paths, fallback: false }
}
SSG(Static-site Generator)
페이지에서 getStaticProps 를 호출하면, Next.js는 getStaticProps에서 반환한 props를 사용하여 빌드 시간에 페이지를 pre-render 한다.
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
getStaticProps는 언제 사용해야 할까? 다음과 같은 경우에 사용할 수 있을 것이다.
- 페이지를 렌더링하는데 필요한 데이터가 사용자의 요청 전에 빌드 시간에 가능할 때
- 데이터가 headless CMS에서 불러와질 때
- 페이지가 SEO를 위해 pre-render 되어야 하고 빠르게 실행되어야 할 때
- 데이터가 공개적으로 캐싱 될 수 있을 때
getStaticProps는 그렇다면 언제 실행이 될까? getStaticProps는 항상 서버에서 실행이 되고 클라이언트에서는 실행되지 않는다. 항상 next build 동안에 실행이 된다.
- 만약 fallback: true 이면 백그라운드에서 실행이 된다.
- 만약 fallback: blocking이면 최초 렌더링 이전에 호출이 된다.
- revalidate 를 사용하면 백그라운드에서 실행이 된다.
- revalidate() 를 사용하면 요청에 맞게 백그라운드에서 실행이 된다.
Incremental Static Regeneration과 결합하여, getStaticProps는 오래된 페이지가 갱신될 때 백그라운드에서 실행된다. 그리고 나서 새로운 페이지가 브라우저에서 보여진다.
getStaticProps 는 정적인 HTML을 만들 때 쿼리 파라미터나 HTTP 헤더처럼 인입되는 요청에 접근할 수 없다.
아래의 예제는 CMS에서 블로그 포스트 리스트를 어떻게 패치하는지 보여준다.
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// You can use any data fetching library
const res = await fetch('https://.../posts')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
getStaticProps는 서버 사이드에서 실행이 되므로, 클라이언트 사이드에서는 절대 실행되지 않는다. 브라우저의 자바스크립트 번들에도 포함되지 않으며, 따라서 직접적인 데이터베이스 쿼리를 브라우저에 보내지 않고 작성할 수 있다.
이는 getStaticProps에서 API Route를 통해 패칭하는 대신, 서버 사이드 코드를 바로 getStaticProps에 작성할 수 있다는 의미이다. 아래의 예제는 데이터를 CMS에서 패칭하는 로직이 lib/ 디렉토리에 공유가 되고 이 로직이 getStaticProps와 공유될 수 있음을 나타내는 예제 코드이다.
// lib/load-posts.js
// The following function is shared
// with getStaticProps and API routes
// from a `lib/` directory
export async function loadPosts() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts/')
const data = await res.json()
return data
}
// pages/blog.js
import { loadPosts } from '../lib/load-posts'
// This function runs only on the server side
export async function getStaticProps() {
// Instead of fetching your `/api` route you can call the same
// function directly in `getStaticProps`
const posts = await loadPosts()
// Props returned will be passed to the page component
return { props: { posts } }
}
대안적으로, 데이터를 패치할 때 API Route를 사용하지 않으면 fetch() API가 getStaticProps 안에서 직접 데이터를 패치할 때 사용될 수 있다.
getStaticProps 로 빌드 시간에 페이지가 pre-render 될 때, Next.js는 HTML 파일 뿐만 아니라 JSON 파일을 만들어서 실행중인 getStaticProps에서 반환시킨다. 이 JSON 파일이 클라이언트 사이드 라우팅에서 사용될 수 있다. getStaticProps로 pre-render된 페이지를 탐색할 때, Next.js는 이 JSON 파일을 패치하여 페이지 컴포넌트에 props로 보낸다. 이는 클라이언트 사이드 트랜지션은 getStaticProps를 부르지 않으며 오직 추출된 JSON만 사용한다는 의미이다.
참고자료
https://nextjs.org/docs/basic-features/data-fetching/overview
'Prog. Langs & Tools > Next.js' 카테고리의 다른 글
Next.js에 대해서 알아보자 #3. Dynamic Routes와 SSG (0) | 2021.01.18 |
---|---|
Next.js에 대해서 알아보자 #2. Data Fetching (0) | 2020.11.29 |
Next.js에 대해서 알아보자 #1. Intro (0) | 2020.11.12 |