들어가며#
Next.js 15에서는 캐시 전략에 아주 큰 방향 전환이 있었는데요.
기존의 기본으로 캐시하기 방식에서, 이제는 명시적으로 캐시를 제어하는 방향으로 바뀌었습니다.
이 변화 덕분에 개발자는 캐시를 더 세밀하게 제어할 수 있게 되었지만, 반대로 성능을 극대화하려면 각 캐시 계층의 특징을 정확히 이해하고 제대로 활용해야만 합니다.
이번 글에서는 Next.js 15의 여러 캐시 계층이 각각 어떤 역할을 하는지 정리하고, 실전에서 바로 써먹을 수 있는 캐시 전략을 자세히 알아볼 건데요.
최적의 캐시 전략을 구현함으로써 빠르고 쾌적한 사용자 경험을 만들어낼 수 있을 겁니다.
기본 방침#
Next.js 15에서는 다음과 같은 기본 방침에 따라 캐시 전략을 설계해야 하는데요.
바로 가능한 한 캐시를 사용하되, 필요할 때만 데이터를 다시 가져오거나 다시 렌더링한다는 것입니다.
Next.js 15부터는 데이터 캐시(Data Cache)나 라우터 캐시(Router Cache)가 기본적으로 꺼지고, 캐시는 이제 옵트인(Opt-in), 즉 명시적으로 켜야만 동작하게 되었거든요.
하지만 성능을 생각한다면 가능한 한 캐시를 적극적으로 활용하고, 꼭 필요할 때만 데이터를 다시 가져오거나 렌더링하는 것이 최상의 사용자 경험을 제공하는 방법입니다.
다만, 캐시 제어를 잘못하면 오래된 데이터가 보이거나, UI와 DB의 상태가 맞지 않는 심각한 문제가 생길 수 있으니 최적화는 신중하게 진행해야 합니다.
Full Route Cache 규칙#
Full Route Cache는 빌드 시점에 페이지의 렌더링 결과를 미리 캐시해두어, 요청이 올 때마다 렌더링할 필요가 없게 만드는 기능인데요.
전통적인 정적 사이트 생성(SSG)과 거의 같다고 생각하시면 됩니다.
이 기능을 제대로 활용하려면, 루트 그룹을 사용해서 정적 페이지와 동적 페이지의 디렉토리를 분리하고, 각각에 전용 layout.tsx를 배치해야 하는데요.
이렇게 하면 한 페이지의 동적 요소 때문에 애플리케이션 전체가 동적으로 렌더링되는 비극을 막을 수 있습니다.
Router Cache 규칙#
Router Cache는 클라이언트 측에서 페이지를 이동할 때의 캐시를 관리하는 기능인데요.
Next.js 15에서는 페이지 이동 시 항상 최신 데이터가 반영되도록 기본 설정이 변경되었습니다.
현재로서는 Router Cache는 사용하지 않는 것을 원칙으로 하는데요.
왜냐하면 next.config.ts 파일에서 프로젝트 전체에 대해 한 번에 설정하는 것밖에 안 되기 때문입니다.
애플리케이션에는 정적인 콘텐츠 페이지도 있고, 실시간성이 중요한 대시보드 페이지도 섞여있기 마련이거든요.
모든 페이지에 캐시 기간을 똑같이 설정하면, 실시간성이 필요한 페이지에서 오래된 데이터가 보이는 위험이 있습니다.
Data Cache 규칙#
Data Cache는 서버 측에서 fetch 요청의 결과를 캐시하는 기능인데요.
Next.js 15부터 기본적으로 비활성화되었지만, 서버 컴포넌트에서 데이터를 가져올 때는 이 Data Cache를 적극적으로 사용하는 것이 기본 방침입니다.
다만, 아래의 규칙들을 반드시 지키면서 신중하게 운영해야 합니다.
- 요구사항이 불명확할 땐 일단 끄기
개발 초기나 요구사항이 아직 확정되지 않은 단계에서는, 일단 데이터 캐시를 끈 상태로 개발하고 나중에 최적화하는 접근을 추천합니다.
- 다른 사용자가 수정할 수 있는 데이터는 끄기
여러 사용자가 함께 사용하는 데이터, 예를 들어 SNS의 게시물이나 채팅 메시지 같은 곳에는 데이터 캐시를 사용하지 않는데요.
다른 사용자가 올린 최신 정보가 보이지 않을 위험이 있기 때문입니다.
revalidatePath대신revalidateTag사용하기
데이터 캐시를 갱신할 때는
revalidatePath대신revalidateTag를 사용해야 하는데요.따라서 모든
fetch함수에는 반드시next.tags옵션을 붙이는 것을 규칙으로 삼아야 합니다.revalidatePath는 지정된 경로의 캐시 전체를 한꺼번에 날려버리기 때문에, 굳이 갱신할 필요가 없는 데이터 캐시까지 삭제해서 불필요한 API 요청을 유발할 수 있습니다.반면,
revalidateTag는 태그를 지정해서 필요한 부분만 정확히 콕 집어 갱신할 수 있거든요.예를 들어 게시물을 수정하면, 그 게시물의 캐시만 삭제한다와 같은 세밀한 제어가 가능해집니다.
Request Memoization 규칙#
Request Memoization은 한 번의 렌더링 과정 안에서 동일한 fetch 요청이 여러 번 발생하면, 그 결과를 자동으로 재사용하는 똑똑한 기능인데요.
예를 들어, 여러 컴포넌트에서 같은 API를 호출하더라도 실제 네트워크 요청은 단 한 번만 일어나고 그 결과가 공유되는 것입니다.
이 기능은 개발자가 신경 쓰지 않아도 기본적으로 적용되고, 따로 제어할 방법도 없기 때문에, 특별한 설정 없이 그냥 그대로 사용하면 됩니다.
클라이언트 캐시 규칙 (TanStack Query)#
클라이언트 측에서 데이터를 가져올 때는 TanStack Query(React Query)의 캐시 기능을 사용하는 것이 좋은데요.
TanStack Query는 캐시 관리, 요청 중복 제거, 백그라운드 업데이트 등 클라이언트 데이터 로딩에 필요한 거의 모든 기능을 제공합니다.
주요 규칙은 다음과 같습니다.
- 데이터 캐시와 함께 사용하지 않기
TanStack Query와Next.js의fetch를 함께 사용해야 할 때(예: 서버 컴포넌트에서 프리페치할 때)는, 데이터 캐시를 꺼야 합니다.두 개의 캐시 계층을 동시에 사용하면 캐시의 일관성을 관리하기가 굉장히 복잡해지기 때문인데요.
결국 어떤 캐시가 진짜 최신 데이터지? 하는 혼란을 피하기 위해,
TanStack Query의 클라이언트 캐시로 창구를 단일화하는 것입니다.- 캐시 갱신은
invalidateQueries를 기본으로 사용
캐시를 갱신할 때는 기본적으로
invalidateQueries를 사용하는데요.가장 단순하면서도 UI와 DB의 데이터 정합성을 확실하게 보장할 수 있는 방법이기 때문입니다.
- 캐시 갱신은
- SSR + Hydration 시 이중 로딩 피하기
서버 컴포넌트에서 데이터를 미리 가져와 클라이언트로 넘겨줄(Hydration) 때, 서버와 클라이언트에서 같은 데이터를 두 번 요청하지 않도록 주의해야 하는데요.
queryKey가 서버와 클라이언트에서 일치하는지,staleTime이 0보다 큰 값으로 설정되었는지 등을 꼼꼼히 확인해야 합니다.
캐시 계층 정리#
Next.js 15에서는 여러 캐시 계층이 서로 협력하며 동작하는데요.
각각의 역할과 특징을 이해하면 더 나은 캐시 전략을 설계할 수 있습니다.
| 캐시 이름 | 위치 | 목적 | 기간 | 제어 방법 |
|---|---|---|---|---|
| Request Memoization | 서버 | 렌더링 중 중복 fetch 제거 | 렌더링 중 | 자동 (제어 불가) |
| Data Cache | 서버 | 서버 fetch 결과 캐시 | 영구적 (갱신 전까지) | next.revalidate, revalidateTag |
| Full Route Cache | 서버 | 렌더링 결과 캐시 (SSG) | 영구적 (재배포 전까지) | 동적 API 사용 여부, revalidatePath |
| Router Cache | 클라이언트 | 페이지 이동 시 캐시 | 세션 중 | next.config의 staleTimes |
| TanStack Query Cache | 클라이언트 | 클라이언트 fetch 결과 캐시 | 설정에 따름 | staleTime, gcTime, invalidateQueries |
캐시 전략 베스트 프랙티스#
지금까지 설명한 내용을 바탕으로, 실전에서 바로 적용할 수 있는 베스트 프랙티스를 정리해봤는데요.
이 네 가지만 기억해도 충분합니다.
- 서버 로딩은 데이터 캐시를 기본으로 사용
서버 컴포넌트에서는 데이터 캐시를 적극적으로 활용하는 것이 좋습니다.
revalidateTag로 세밀하게 갱신
데이터 캐시를 갱신할 때는 필요한 부분만 정확히 갱신할 수 있는
revalidateTag를 우선적으로 사용해야 합니다.- 정적 페이지에서는 Full Route Cache 활용
정적인 콘텐츠 페이지에서는 Full Route Cache를 활성화해서 성능을 극대화하는 것이 좋습니다.
- 클라이언트 로딩은 TanStack Query로 통일
클라이언트 측의 데이터 로딩과 캐시는
TanStack Query로 일원화해서 관리하는 것이 가장 좋습니다.
마치며#
이번 글에서는 Next.js 15의 캐시 전략에 대한 전체적인 그림과 실전 활용법에 대해 알아봤는데요.
Next.js 15에서는 캐시 정책이 기본으로 캐시에서 명시적 제어로 바뀌었습니다.
이 변화로 인해 개발자는 캐시를 더 세밀하게 제어할 수 있게 되었지만, 동시에 각 캐시 계층의 특징을 제대로 이해하는 것이 그 어느 때보다 중요해졌거든요.
캐시는 성능과 사용자 경험을 좌우하는 핵심 요소입니다.
각 캐시의 역할을 정확히 이해하고 상황에 맞게 잘 활용한다면, 정말 빠르고 쾌적한 웹 애플리케이션을 만들 수 있을 겁니다.
끝까지 읽어주셔서 정말 감사합니다.