7

Next.js 서버 렌더링 전략

Next.js App Router로 웹 애플리케이션을 개발할 때 활용할 수 있는 서버 렌더링 전략에는 크게 두 가지가 있습니다.

  1. 정적 렌더링(Static Rendering)
  2. 동적 렌더링(Dynamic Rendering)

각 렌더링 전략은 무엇이 다른지, 어떻게 변경할 수 있는지 알아보겠습니다.

정적 렌더링(Static Rendering)

정적 렌더링을 활용하면 페이지를 빌드 타임에 생성하고 생성된 페이지를 CDN과 같은 중간 서버에 캐시 할 수 있습니다. 그 후 사용자가 페이지를 요청하면 서버에 직접 요청하는 대신 사용자와 가장 가까운 CDN으로부터 캐시 된 응답을 받게 됩니다.

정적 렌더링은 빌드 타임에 페이지를 생성합니다.
정적 렌더링은 빌드 타임에 페이지를 생성합니다.

사용자의 요청에 빠르게 응답할 수 있다는 장점을 가지고 있지만, 빌드 타임에 페이지를 생성해야 하기 때문에 어떤 데이터를 보여줘야 할지 미리 알 수 있는 경우 활용하기에 좋습니다. 예를 들어 공식 문서 페이지, 관리자가 작성하는 블로그 페이지의 경우 어떤 데이터를 보여줘야 할지 빌드 타임에 알 수 있으며 사용자가 요청함에 따라 변하지 않기 때문에 정적 렌더링을 활용하기에 좋습니다.

동적 렌더링(Dynamic Rendering)

하지만 실제로 서비스를 개발하다 보면 빌드 타임에 페이지를 렌더링 하는데 필요한 모든 데이터를 미리 알 수 있는 경우는 많지 않습니다. 예를 들어, 이커머스 서비스에서 사용자에 따라 맞춤화된 추천상품을 보여줘야 하는 경우 빌드 타임에는 사용자가 누구일지 알 수 없기 때문에 미리 페이지를 생성할 수 없습니다. 이 경우에는 런타임에(혹은 사용자 요청 시) 페이지를 생성하는 동적 렌더링 전략을 활용해야 합니다.

서버를 하나만 사용했을 경우 다음과 같이 나타낼 수 있습니다.

동적 렌더링은 런타임에 페이지를 생성합니다.
동적 렌더링은 런타임에 페이지를 생성합니다.

런타임에 페이지를 생성하기 때문에 정적 렌더링보다는 지연 시간이 길지만 런타임만 알 수 있는 데이터 활용하여 페이지를 생성할 수 있습니다. 예를 들어, 사용자가 누구인지 확인하고 해당 사용자가 좋아할 만한 추천 상품을 페이지에 포함하여 응답을 보내는 것이 가능해집니다.

Next.js의 렌더링 전략

기본적으로 Next.js는 옵션을 따로 설정하지 않으면 자동으로 정적, 동적 렌더링중 더 적합한 전략으로 페이지를 생성해 줍니다.

정적 렌더링 - 디폴트

기본적으로 Next.js는 퍼포먼스를 위해 정적 렌더링을 기본 렌더링 전략으로 선택합니다.

동적 렌더링

빌드 타임에 알 수 없는 값을 사용할 경우

빌드 타임에 미리 알 수 없는 데이터를 사용하는 경우, 즉, 런타임에만 알 수 있는 값(동적 데이터)을 사용할 경우 동적 렌더링 전략을 사용하도록 변경합니다.

런타임에만 알 수 있는 값들은 다음과 같습니다.

  1. HTTP 쿠키
  2. HTTP 헤더
  3. URLSearchParams

페이지를 렌더링하는 코드에 위 값을 읽는 코드가 포함되어 있을 경우 Next.js는 해당 페이지를 런타임에 생성하도록(동적 렌더링) 변경합니다. 실제로는 Next.js가 동적 데이터를 읽었는지 트레킹해야하기 때문에 다음과 같이 Next.js에서 제공하는 API를 사용해야 합니다.

  1. cookies
  2. headers
  3. 페이지 컴포넌트의 props

Next.js 코드에서 cookies 함수를 확인해 보면 trackDynamicDataAccessed 함수로 동적 데이터 접근을 트레킹하고 있다는 것을 확인해 볼 수 있습니다.

export function cookies() {
// 생략
if (staticGenerationStore) {
if (staticGenerationStore.forceStatic) {
// force-static 옵션을 사용했을 경우 cookies 함수를 사용해도 동적 데이터에 접근한 것을 트레킹하지 않음
return RequestCookiesAdapter.seal(new RequestCookies(new Headers({})))
} else {
// 아닐 경우 동적 데이터에 접근 한 것을 트레킹 함
trackDynamicDataAccessed(staticGenerationStore, callingExpression)
}
}
// 생략
}

동적 라우트 (Dynamic Routes)를 사용할 경우

동적 라우트를 사용하는 페이지의 경우, 빌드 타임에는 어떠한 categorySlug가 있는지 모르기 때문에 동적 렌더링으로 페이지를 생성합니다.

// `/category/[categorySlug]/page.tsx`
export default function Page({ params }: { params: { `categorySlug`: string } }) {
const category = await fetchCategory(params.categorySlug);
return ...;
}

동적 라우트를 사용하면서 정적 렌더링으로 페이지를 생성하고 싶다 generateStaticParams 를 사용하여 어떤 params로 정적 페이지를 생성할지 Next.js에게 알려줘야 합니다.

캐시 되지 않은 데이터를 사용할 경우

다음과 같이 fetch API를 no-store 라는 cache 옵션과 함께 사용할 경우 캐시를 저장하지도 않고 찾아보지도 않기 때문에 항상 서버에서 최신 리소스를 가져옵니다. 만약 빌드 타임에 페이지를 생성한다면 최신 리소스를 가져올 수 없기 때문에 이 경우에도 Next.js는 자동으로 동적 렌더링 전략을 사용하도록 변경해 줍니다.

export default function Page() {
const data = await fetch("https://jsonplaceholder.typicode.com/todos/1", { cache: "no-store" });
return ...;
}