Next.js의 프리 렌더링 옵션으로는 잘 알려진 SSG와 SSR 두 가지 외에도 ISR라고 불리는 한 가지 방식이 더 존재한다.
이 세 가지 프리 렌더링 방식의 특징과 차이점에 대해 알아보자.
참고로 하나의 애플리케이션에서 하나의 방식만 사용해야 하는 것은 아니며, 필요에 따라 어떤 페이지에선 SSG 방식을 사용하고 나머지 페이지에선 SSR 방식을 사용하는 것도 가능하다.
또한 이 방식들과 클라이언트 사이드 렌더링 방식을 혼합하여 사용하는 것도 가능하다. (즉 어떤 페이지의 특정 부분은 전적으로 클라이언트 사이드에서 렌더링 되도록 하는 방식)
❓Pre-rendering이란?
Next.js에선 기본적으로 모든 페이지를 프리 렌더링한다. 즉 HTML이 클라이언트 사이드에서 자바스크립트를 통해 생성이 되는 게 아니라 Next.js가 사전에 각 페이지를 만들어 놓는다. 이때 각 HTML은 해당 페이지에 최소한으로 필요한 자바스크립트 코드와 결합되어있다. 페이지가 브라우저에 의해 로드될 때는 hydration이라는 프로세스를 거치게 된다. hydration은 자바스크립트 코드가 실행되어 해당 페이지를 완전히 인터렉티브하게 만드는 것을 의미한다.
프리 렌더링을 할 경우, 프리 렌더링을 하지 않을 때보다 더 나은 성능과 SEO를 갖출 수 있다.
*Next.js는 프리 렌더링 및 서버사이드 렌더링을 포함한 페이지 렌더링을 위한 런타임으로 Node.js를 사용한다.
- 아래 내용에서 외부 API로 요청을 보낸다고 표현한 경우를 제외한 '요청'의 의미는, 어떤 '페이지를 요청한다'는 의미로 사용했다.
- /about path 진입 = '어바웃 페이지를 요청한다.'
Static-site Generation (SSG)
- HTML이 빌드 타임에 생성되며, 매 요청 시 재사용 된다.
- SSR 방식보다 성능이 더 좋고 빠르다고 한다.
- 정적으로 생성된 페이지는 한 번 생성되면 CDN에 캐시될 수 있기 때문에, SSR 방식을 통해 매 요청마다 페이지를 생성하는 경우보다 훨씬 빠르다고 한다.
- 외부 데이터가 없는 경우와 있는 경우 모두 정적 페이지 생성이 가능하다.
- 데이터가 없는 경우 (예: 회사 소개 페이지)
- Next.js는 외부 데이터를 가져올 필요가 없는(=data fetch를 하지 않는) 페이지는 SSG 방식으로 프리 렌더링한다.
- 데이터가 있는 경우 - 두 가지 시나리오가 있는데, 하나만 적용할 수도 있고 둘 다 적용할 수도 있다.
- 시나리오1: 페이지의 내용이 외부 데이터에 의존한다면 getStaticProps를 쓸 것 (예: CMS로부터 블로그 포스트 목록을 가져오려고 할 때)
- 시나리오2: 페이지의 경로가 외부 데이터에 의존한다면 getStaticPaths를 쓸 것 (예: 동적 라우트(dynamic routes)로 pages/posts/[id].js 페이지를 생성하려는 경우 id 값에 들어갈 값들을 먼저 받아와야 할 때)
- 데이터가 없는 경우 (예: 회사 소개 페이지)
/* 시나리오1 */
function Blog({ posts }) {
// 포스트 목록 렌더링...
}
// getStaticProps는 빌드 시에 호출된다.
export async function getStaticProps() {
// 블로그 포스트들을 가져오기 위해 외부 API 엔드포인트를 통해 요청을 보낸다.
const res = await fetch('https://.../posts')
const posts = await res.json()
// { props: { posts } } 객체를 반환하면 블로그 컴포넌트가 빌드 타임에
// `posts`를 prop으로 전달 받게 된다.
return {
props: {
posts,
},
}
}
export default Blog
/* 시나리오2 */
function Post({ post }) {
// 개별 포스트 렌더링..
}
// getStaticPaths는 빌드 시에 호출된다.
export async function getStaticPaths() {
// 블로그 포스트들을 가져오기 위해 외부 API 엔드포인트를 통해 요청을 보낸다.
const res = await fetch('https://.../posts')
const posts = await res.json()
// 프리 렌더링 해야 하는 페이지 경로를 `posts`로부터 뽑아낸다.
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// 이렇게 { paths, fallback: false } 객체를 반환하면,
// `paths`에 포함된 페이지들만 빌드 시에 프리 렌더링 된다.
// { fallback: false }에 따라 다른 라우트는 404가 된다.
return { paths, fallback: false }
}
// getStaticProps도 역시 빌드 시에 호출된다.
export async function getStaticProps({ params }) {
// params에는 포스트의 `id`가 포함되어 있다.
// (예를 들어 라우트가 /posts/1일 경우, params.id는 1)
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// 포스트 데이터를 페이지에 props로 전달한다.
return { props: { post } }
}
export default Post
- SSG 방식은 언제 사용하는 것이 좋은가?
- 마케팅 페이지
- 블로그 포스트
- 이커머스 상품 목록
- 등등.. 사용자의 요청에 앞서 프리 렌더링해도 되는 페이지
- 다음의 경우에는 SSG 방식이 적합하지 않다.
- 사용자의 요청에 앞서 프리 렌더링할 수 없는 페이지
- 예를 들어, 페이지가 빈번하게 변경 된다거나, 페이지의 내용이 매 요청마다 변경되어야 하는 페이지
- SSG 방식이 적합하지 않은 경우의 대안
- SSG와 클라이언트 사이드 렌더링 방식 함께 사용하기: 페이지의 특정 부분에선 프리 렌더링을 하지 않고, 그런 부분은 클라이언트 사이드에서 자바스크립트를 통해 생성되도록 한다.
- SSR 사용하기: SSR 방식을 사용하여 생성하는 페이지는 CDN에 캐시될 수는 없어 속도는 느려질 수 있지만, 매 요청마다 다시 프리 렌더링되기 때문에 늘 최신화(up-to-date) 된 데이터를 보여줄 수 있다.
Server-side Rendering (SSR)
- HTML이 매 요청마다 생성된다.
- getServerSideProps를 사용하여 서버사이드 렌더링을 할 수 있다.
- getServerSideProps vs getStaticProps
- getServerSideProps 👉 매 요청 시에 실행된다.
- getStaticProps 👉 빌드 시에 실행된다. (즉 딱 한 번 실행된다.)
function Page({ data }) {
// data를 렌더링..
}
// getServerSideProps는 매 요청 시 호출된다.
export async function getServerSideProps() {
// 외부 API로부터 데이터를 가져온다.
const res = await fetch(`https://.../data`)
const data = await res.json()
// 이렇게 가져온 데이터를 페이지에 props로 전달한다.
return { props: { data } }
}
export default Page
- 공식 문서에선 SSR이 SSG에 비해 느리기 때문에 절대적으로 필요한 경우에만 사용할 것을 권고하고 있다.
Incremental Static Regeneration (ISR)
- stale-while-revalidate 캐싱 전략을 따르는 일종의 하이브리드 방식이다.
- ISR을 사용하면 SSR을 사용할 때보다 성능을 향상시킬 수 있고 사용성도 개선할 수 있다고 한다.
- ISR은 페이지 단위로 정적 생성되도록 하는 방식이다. 즉, 전체 사이트를 다시 빌드하지 않아도 된다는 것이다.
- 어떻게 사용하는가?
- getStaticProps에 revalidate prop을 추가해주면 된다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// getStaticProps는 서버사이드에서 빌드 시에 호출된다.
// 유효성을 재확인(revalidation)하도록 설정해두었다면, 새로운 요청이 들어올 때 다시 호출된다.
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js는 요청 후 10초가 지난 이후에 또 요청이 들어오면 페이지를 다시 생성하려는 시도를 한다.
revalidate: 10, // 단위: 초
}
}
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
return { paths, fallback: 'blocking' }
}
export default Blog
- 어떻게 동작하는가? (위 예제 코드 기반으로 설명)
- 빌드 타임에 프리 렌더된 페이지에 대해 요청이 발생하면, 우선은 초기에는 캐시된 페이지를 보여준다.
- 이 페이지에 대한 최초 요청 이후 10초 내로 들어오는 모든 요청들은 캐시된다.
- 10초 이후 들어오는 요청에도 일단은 캐시된(즉, stale한) 페이지가 표시된다.
- 요청이 들어온 순간 재생성을 시작하기 때문에 그 전까지 캐시된 페이지를 보여주는 것이다.
- Next.js는 백그라운드에서 페이지를 재생성하기 시작한다.
- 페이지 재생성이 성공하면, Next.js는 캐시된 것은 더 이상 유효하지 않도록 만들고, 업데이트된 페이지를 보여준다.
- 페이지 재생성이 실패하면, 기존 페이지가 변경되지 않고 계속 보여진다.
- 만약 10초(즉 revalidate 기준 시간)가 흐른 뒤에도 해당 페이지에 대한 새로운 요청이 발생하지 않는다면 페이지 재생성은 이루어지지 않는다.
참고
'학습 내용 > Front-End' 카테고리의 다른 글
[Next.js] Next.js 13 - Data Fetching, Server Components (0) | 2023.01.20 |
---|---|
intermock을 사용하여 mock data 만들기 (0) | 2022.07.10 |
Luhn algorithm(룬 알고리즘)으로 유효한 카드 번호인지 확인하기 (0) | 2022.05.10 |
[React] 리액트 면접 질문 (기초 개념) (0) | 2022.03.08 |
Safari에만 적용되는 코드를 짜고 싶다면? (0) | 2021.10.06 |