| 이 포스팅은 Next.js 문서 번역 및 정리한 내용입니다.
기본적으로 Next.js는 모든 페이지를 pre-render한다. React를 단독으로 사용할 때 클라이언트측에서 JavaScript로 렌더링하는 대신 각 페이지에 대해 미리 HTML을 생성한다. pre-rendering을 사용하면 성능과 SEO가 향상될 수 있다.
생성된 각 HTML은 페이지를 위해 필요한 최소한의 JavaScript로 결합하게 되는데 이것을 Hydration이라고 부른다. 브라우저에서 페이지가 로드될 때, JavaScript가 실행되고 페이지를 인터렉티브하게 만든다. Hydration이 완료되어야 이벤트나 로직이 실행될 수 있다.
Next.js에는 정적 생성과 서버 측 렌더링이라는 두 가지 형태의 pre-rendering이 있다. 차이점은 페이지의 HTML을 생성하는 시점에 있다.
Next.js는 각 페이지마다 사용할 pre-rendering 방식을 선택할 수 있다. 그래서 대부분의 페이지에서 Static Generation을 사용하고 다른 페이지는 Server-side Rendering을 사용해서 하이브리드 Next.js 앱을 만들 수 있다.
성능상의 이유로 Server-side Rendering 보다 Static Generation을 사용하는 것이 좋으며 성능을 높이기 위해 정적으로 생성된 페이지를 CDN으로 캐시할 수 있다. 하지만 몇몇 케이스에서 Server-side Rendering이 유리한 선택일 수 있다.
📎Next.js는 useEffect나 서버사이드 함수를 사용하지 않는 이상 기본적으로 SSG로 렌더링한다.
📎next.js는 기본적으로 pre-rendering을 지원한다. 프리렌더링은 정적 생성, 동적 생성 두 가지로 나뉘는데 정적 생성은 빌드 타임에 HTML을 생성하는 것이고 후자는 서버단에서 HTML을 생성하는 것이다. 브라우저에서 페이지를 로드할 때 js 파일을 받아와 실행시키고 pre-rendering된 HTML과 js 파일을 결합해서 사용자와 상호작용가능하도록 만드는 작업을 Hydration이라고 부르는데 마치 마른 대지에 물을 뿔려주는것같은 단어이다.
getStaticProps
페이지에서 getStaticProps
함수를 반환하면 빌드 타임에 pre-rendering 한다.
getStaticProps
을 사용해야 하나?getStaticProps
는 언제 실행되나요?getStaticProps
는 항상 서버에서 실행되고 클라이언트에서는 실행되지 않는다. getStaticProps로 작성한 코드는 클라이언트측 번들에서 제거된다.
getStaticProps
는 항상 next build
하는 동안 실행된다.getStaticProps
는 fallback:true
를 사용했을 때 백그라운드에서 실행된다.getStaticProps
는 fallback:blocking
을 사용할 때 초기 렌더링 전에 호출된다.getStaticProps
는 revalidate를 사용할 때 백그라운드에서 실행된다.ISR과 결합해서 사용할 때, getStaticProps
는 stale page가 revalidate되는 동안 백그라운드에서 실행되고 새로 생성된 페이지를 브라우저에 제공한다.
getStaticProps
는 정적 HTML을 생성하기 때문에 클라이언트의 쿼리 파라미터, HTTP Header에 접근할 수 없다. 이러한 데이터에 접근하려면 미들웨어 사용을 고려해야한다.
getStaticProps
는 서버측에서 실행되므로 클라이언측에서 실행되지 않고 직접 데이터베이스 쿼리를 작성할 수 있다.
즉 getStaticProps
에서 API route를 가져오는 대신 서버측 코드를 직접 작성할 수 있다.
그리고 getStaticProps
에서 API Routes로 직접 호출하는 것은 서버 내에서 추가 호출이 발생하기 때문에 성능이 저하된다. API Routes 없이 직접 데이터를 가져오는 것이 더 효율적이다.
getStaticProps
로 pre-rendering된 페이지는 HTML 외에 JSON 파일도 생성한다.
이 JSON 파일은 next/link
또는 next/router
를 통한 클라이언트 측 라우팅에 사용된다. getStaticProps
를 사용해서 프리렌더링된 페이지로 이동할 때, Next.js는 JSON 파일을 fetch하고 page component의 props로 받아 사용한다. 이는 export한 JSON만 오직 사용하기 때문에 getStaticProps를 호출하지 않는다는 것을 의미한다.
ISR을 사용하는 경우, getStaticProps
는 클라이언트측 navigation을 위해 필요한 JSON을 백그라운드에서 생성한다.
✍️ getStaticProps
는 빌드 타임에 실행되는 함수이며 이때 JSON으로 반환된다. 그래서 블로그나 랜딩 페이지처럼 정적인 데이터를 표시하는 페이지에서 사용하면 빌드 시점에 이미 HTML로 생성되어 있기 때문에 매우 빠르게 사용자에게 렌더링할 수 있다. 그런데 ISR로 생성하는 경우 fallback 옵션에 따라 서버사이드에서 다시 렌더링되기도 한다.
페이지에 동적 경로가 있고 getStaticProps
를 사용하는 경우 정적으로 생성될 경로 목록들을 정의해줘야 한다.
동적 경로를 사용하는 페이지에서 getStaticPaths
함수를 내보내면 지정된 모든 경로를 pre-rendering 한다.
getStaticPaths
를 사용하나?동적 라우팅을 사용하면서 정적으로 pre-rendering을 하고 싶을 때 사용한다.
getStaticPaths
가 실행되나?빌드 타임에 실행되기 때문에 runtime는 호출되지 않는다. 즉 client-side 번들에는 해당 소스가 포함되지 않는다.
getStaticPaths
는 어디에서 사용하나?getStaticProps
와 반드시 함께 사용해야한다.getServerSideProps
에는 사용할 수 없다.빌드 시에 미리 생성되지 않은 경로에 사용자가 접근했을 때 어떻게 동작할 것인지 결정하는 옵션
fallback: false
: 빌드 시 지정한 경로 외의 모든 경로는 404 페이지로 처리fallback: true
: 백그라운드에서 동적으로 HTML을 생성하고 이 동안 사용자는 로딩 상태를 보게 된다.fallback: blocking
: 백그라운드에서 동적으로 HTML을 생성하고, 이 동안 사용자가 기다렸다가 페이지를 서빙받는다.✍️ getStaticPaths
는 빌드 시점에 렌더링을 할 건데 동적인 경로들을 가질 때 사용하는 함수이다. fallback 옵션을 통해 지정한 경로만 허용할 것인지, 동적으로 다시 생성할 것인지, 이 과정에서 로딩을 보여줄 것인지 말것인지 정할 수 있다. 그리고 많은 양의 동적 경로를 생성하게 되면 빌드 시간이 지연되게 되는데 이는 아래처럼 path를 대괄호 처리해서 요청 시점에 생성하도록 해결할수도 있다.
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',
}
}
요청 시점에 데이터를 가져오고 페이지의 내용을 렌더링하는 데 사용할 수 있는 Next.js 함수
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
type Repo = {
name: string
stargazers_count: number
}
export const getServerSideProps = (async () => {
// Fetch data from external API
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const repo: Repo = await res.json()
// Pass data to the page via props
return { props: { repo } }
}) satisfies GetServerSideProps<{ repo: Repo }>
export default function Page({
repo,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<main>
<p>{repo.stargazers_count}</p>
</main>
)
}
getServerSideProps
를 사용하면 좋을까?개인화된 사용자 요청 데이터, 요청 시점에 최신 데이터를 보여줘야하는 경우
getServerSideProps
는 서버에서 실행된다.getServerSideProps
는 오직 페이지에서만 사용할 수 있다.getServerSideProps
는 JSON을 반환한다.getServerSideProps
가 요청 시점에 데이터를 가져와 초기 HTML을 렌더링한다.getServerSideProps
가 실행된다. -> 해당 객체를 통해 서버에서 페이지를 렌더링해서 다음 페이지를 보여주는 것이기 때문에 <a>
링크나 window.location 사용시 화면 전환시 깜빡거리는 현상이 발생한다. getServerSideProps
를 사용할 때 CMS, DB를 직접 호출할 수 있다.getServerSideProps
내부에서 오류 발생시 pages/500.js
파일이 표시된다.
caching header 설정을 통해 캐싱할 수 있다. cache-control을 직접 제어하기 전에 ISR이 더 적합한 방법인지 고려해보는 것을 추천한다.
export async function getServerSideProps({ req, res }) {
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
return {
props: {},
}
}
브라우저가 로드된 후 Javascript 파일을 실행시켜서 HTML을 만드는 방식이다. 초기 실행시 사용자가 페이지를 보기까지 시간이 걸릴 수 있다는 단점이 있으나 그 뒤부터는 페이지 이동시 깜빡임 없는 부드러운 화면전환을 제공한다는 장점이 있다.
next.js에서 CSR을 사용하는 방법은
1. useEffect()
2. SWR
or TanStack Query
와 같은 라이브러리
CSR로만 렌더링을 하게 되면 SEO에 불리하다는 단점이 있고 초기 사용자가 페이지를 보기까지 시간이 걸리기 때문에 CSR과 SSR을 결합하여 하이브리드로 사용하기를 장려하고 있다.
사이트를 빌드한 후에도 정적 페이지를 생성하거나 업데이트할 수 있게 해준다.
ISR을 사용하려면 getStaticProps
에 revalidate props를 추가한다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// 이 함수는 서버 측에서 빌드 시에 호출.
// 새 요청이 들어오면 revalidate가 활성화된 경우 서버리스 함수에서 다시 호출될 수 있다.
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
revalidate: 10,
}
}
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// 포스트를 기반으로 사전 렌더링할 경로를 가져온다.
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// 이 경로들만 빌드 시에 사전 렌더링.
// { fallback: 'blocking' }은 경로가 존재하지 않는 경우 요청 시에 페이지를 서버 렌더링한다.
return { paths, fallback: 'blocking' }
}
export default Blog
Good to know : CDN 캐싱이 활성화되어있는지 확인해야한다.