Skip to main content
  1. Guides 리스트/
  2. Next.js 답게 개발하기: 앱 라우터의 설계 원리와 실전 가이드/

Next.js 캐싱 정복 1편 - Full Route Cache와 정적 렌더링

·549 words·3 mins·
Next.js 답게 개발하기: 앱 라우터의 설계 원리와 실전 가이드 - This article is part of a series.
Part 13: This Article

Next.js에는 총 4개의 캐시 레이어가 존재하는데요.

이 캐시들은 기본적으로 아주 적극적으로 활용되도록 설계되어 있습니다.

  • 리퀘스트 메모이제이션 (Request Memoization)

서버에서 요청 단위로 API 응답 등을 재사용하는 캐시입니다.

  • 데이터 캐시 (Data Cache)

서버에서 여러 사용자나 배포에 걸쳐 API 응답 등을 재사용하는, 영구적인 캐시입니다.

  • 풀 라우트 캐시 (Full Route Cache)

서버에서 렌더링된 HTML과 RSC 페이로드를 저장하는, 영구적인 캐시입니다.

  • 라우터 캐시 (Router Cache)

클라이언트에서 RSC 페이로드를 저장해, 페이지 이동 시 요청을 줄이는 캐시입니다.

리퀘스트 메모이제이션은 요청 단위로만 동작해서 문제가 되는 경우가 거의 없는데요.

하지만 나머지 세 캐시는 훨씬 더 긴 시간 동안 넓은 범위에 영향을 미치기 때문에, 개발자가 그 원리를 정확히 이해하고 제어하지 않으면 예기치 못한 버그로 이어질 수 있습니다.

이제부터는 이 복잡하고 중요한 Next.js의 캐시에 대해 하나씩 파헤쳐 보겠습니다.

정적 렌더링과 풀 라우트 캐시
#

결론부터 말씀드리면, ‘정적 렌더링(Static Rendering)‘은 빌드 시점에 HTML과 RSC 페이로드의 캐시인 ‘풀 라우트 캐시(Full Route Cache)‘를 생성하는데요.

이 캐시는 짧은 주기로도 쉽게 재검증(revalidate)할 수 있으므로, 사용자 개별 정보가 없는 페이지라면 적극적으로 활용하는 것이 좋습니다.

정적 렌더링과 동적 렌더링
#

과거 페이지 라우터 시절에는 SSR, SSG, ISR이라는 3가지 렌더링 방식이 있었는데요.

앱 라우터에서는 이 개념이 ‘정적 렌더링’과 ‘동적 렌더링(Dynamic Rendering)‘으로 재정의되었습니다.

정적 렌더링: 빌드 시점이나 재검증 후에 렌더링 (SSG/ISR과 유사)

동적 렌더링: 사용자 요청 시점에 렌더링 (SSR과 유사)

앱 라우터는 기본적으로 ‘정적 렌더링’을 사용하며, ‘동적 렌더링’은 필요할 때 직접 활성화해줘야 하는데요.

동적 렌더링으로 전환되는 조건은 다음과 같습니다.

1. Dynamic API 사용
#

cookies()headers() 같은 Dynamic API를 사용하면 해당 페이지는 동적 렌더링으로 전환됩니다.

2. 캐시를 사용하지 않는 fetch()
#

fetch() 함수의 옵션으로 cache: "no-store"next: { revalidate: 0 }을 지정하면 동적 렌더링이 됩니다.

v15부터 fetch의 기본 캐시 정책이 변경되었지만, 동적 렌더링으로 바꾸려면 여전히 "no-store"를 명시적으로 지정해줘야 한다는 점에 주의해야 합니다.

// page.tsx
export default async function Page() {
  const res = await fetch("...", {
    // 동적 렌더링을 위해 "no-store"를 명시해야 합니다.
    cache: "no-store",
  });
  // ...
}

3. Route Segment Config 설정
#

page.tsxlayout.tsx 파일에서 export const dynamic = "force-dynamic"; 또는 export const revalidate = 0;을 설정해서 동적 렌더링을 강제할 수 있습니다.

4. connection() 사용
#

컴포넌트 내부에서 headers()나 캐시 없는 fetch를 사용하지 않으면서 동적 렌더링을 강제하고 싶을 때, connection() 함수를 사용할 수 있는데요.

주로 ‘프리즈마(Prisma)’ 같은 ORM으로 DB에 직접 접근할 때 유용합니다.

정적 렌더링을 적극적으로 활용해야 하는 이유
#

정적 렌더링은 안정성과 성능 면에서 아주 뛰어난데요.

Next.js는 이 정적 렌더링의 장점을 극대화하기 위해, 렌더링 결과물인 ‘풀 라우트 캐시’를 아주 유연하게 제어할 수 있는 방법을 제공합니다.

필요할 때만 캐시를 갱신하는 ‘온디맨드 Revalidation’
#

‘서버 액션(Server Actions)‘이나 ‘라우트 핸들러’ 안에서 revalidatePath()revalidateTag() 함수를 호출하면, 특정 경로의 풀 라우트 캐시를 원하는 시점에 바로 갱신할 수 있는데요.

데이터가 변경되었을 때 즉시 화면에 반영해야 하는 경우에 아주 유용합니다.

"use server";

import { revalidatePath } from "next/cache";

export async function action() {
  // ... 데이터 수정 로직

  revalidatePath("/products");
}

시간 기반으로 캐시를 갱신하는 ‘Time-based Revalidation’
#

page.tsxlayout.tsx에서 revalidate 옵션을 설정하면, 지정된 시간마다 캐시를 자동으로 갱신하게 할 수 있는데요.

// layout.tsx | page.tsx
export const revalidate = 10; // 10초마다 캐시를 갱신

이 값을 1초 같은 아주 짧은 시간으로 설정하더라도, 순간적으로 수천 개의 요청이 몰렸을 때 실제 렌더링은 1초에 단 한 번만 일어나게 되거든요.

백엔드 API의 부하를 줄이고 안정적인 성능을 유지하는 데 아주 효과적인 방법입니다.

주의해야 할 점
#

의도치 않은 동적 렌더링 전환은 페이지 전체의 성능 저하로 이어질 수 있거든요.

특히 cookies()를 사용하거나, 데이터 캐시를 끈다는 주된 목적 때문에 부수적으로 동적 렌더링이 활성화될 때는 그 영향 범위를 주의 깊게 살펴야 합니다.

Next.js 답게 개발하기: 앱 라우터의 설계 원리와 실전 가이드 - This article is part of a series.
Part 13: This Article