현록

Next.js 16 Cache Components와 use cache 정리

Next.js 16에서는 App Router의 캐싱 모델을 더 명시적으로 다루기 위한 Cache Components가 들어왔다.
기존에는 route segment config, fetch 옵션, unstable_cache 같은 API를 조합해서 캐싱을 제어했다.
Cache Components를 켜면 데이터는 기본적으로 동적이고, 캐시하고 싶은 page, component, function에 use cache를 붙이는 방식으로 생각할 수 있다.

App Router 캐싱 모델의 변화

Cache Components의 핵심은 “기본은 동적, 필요한 부분만 명시적으로 캐시”라는 방향이다.
cacheComponents를 켜면 Next.js는 정적 HTML shell을 먼저 만들고, 준비되는 동적 콘텐츠는 스트리밍으로 채울 수 있다.
이 구조 덕분에 한 route 안에서 정적인 영역과 동적인 영역을 섞어 다루기 쉬워진다.

이전 모델에서는 dynamic, revalidate, fetchCache 같은 route segment config가 캐싱 판단의 중심이었다.
Cache Components에서는 이런 설정을 use cache, cacheLife, cacheTag로 옮겨가는 흐름이 권장된다.

cacheComponents 활성화

Cache Components는 Next.js 16 기준 기능이다.
프로젝트에서 사용하려면 next.config.tscacheComponents: true를 추가한다.

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

Next.js 15에서 experimental.dynamicIO, experimental.useCache, experimental PPR을 사용했다면 Next.js 16의 cacheComponents로 마이그레이션하는 흐름을 확인해야 한다.
cacheComponents는 PPR, useCache, dynamicIO를 하나의 설정으로 묶는 역할을 한다.

use cache의 역할

use cache는 function, component, file 단위로 캐싱 범위를 선언하는 directive다.
캐시하고 싶은 async function이나 component의 맨 위에 작성한다.

async function getPost(slug: string) {
  'use cache'

  const res = await fetch(`https://api.example.com/posts/${slug}`)

  return res.json()
}

이렇게 하면 함수의 입력과 내부에서 접근한 값을 기준으로 결과를 캐시할 수 있다.
Cache Components를 켠 상태에서는 fetch 안에 cache: 'force-cache'next: { revalidate }를 직접 붙이기보다, 캐시 범위를 use cache로 감싸고 수명과 태그를 별도 API로 표현하는 쪽이 더 명확하다.

cacheLife로 수명 지정

cacheLifeuse cache 범위 안에서 캐시 수명을 지정하는 함수다.
호출하지 않으면 기본 profile이 적용된다.

import { cacheLife } from 'next/cache'

async function getPosts() {
  'use cache'
  cacheLife('hours')

  const res = await fetch('https://api.example.com/posts')

  return res.json()
}

cacheLife는 module scope에서 호출할 수 없다.
캐시가 선언된 function이나 component 안에서 직접 호출해야 한다.
공유 utility로 숨기기보다 캐시 동작이 필요한 위치에 드러내는 편이 읽기 좋다.

cacheTag와 revalidateTag

cacheTag는 캐시된 결과에 태그를 붙이는 함수다.
나중에 특정 태그만 무효화할 수 있어서 게시글, 상품 목록, 문서처럼 일부 데이터만 갱신해야 하는 경우에 유용하다.

import { cacheLife, cacheTag } from 'next/cache'

async function getPost(slug: string) {
  'use cache'
  cacheLife('hours')
  cacheTag('posts', `post:${slug}`)

  const res = await fetch(`https://api.example.com/posts/${slug}`)

  return res.json()
}

태그를 무효화할 때는 Server Function이나 Route Handler에서 revalidateTag를 호출한다.

import { revalidateTag } from 'next/cache'

export async function updatePost(slug: string) {
  await fetch(`https://api.example.com/posts/${slug}`, {
    method: 'PATCH',
  })

  revalidateTag(`post:${slug}`, 'max')
}

Next.js 16 기준으로 revalidateTag는 profile을 함께 전달하는 방식이 권장된다.
'max' profile은 stale-while-revalidate 방식으로 동작해서, 사용자는 오래된 데이터를 먼저 받고 새 데이터는 백그라운드에서 준비된다.

기존 fetch cache와의 차이

이전에는 개별 fetch에 캐싱 의도를 직접 넣는 코드가 흔했다.

await fetch('https://api.example.com/posts', {
  cache: 'force-cache',
  next: {
    revalidate: 3600,
    tags: ['posts'],
  },
})

Cache Components에서는 이 관심사를 함수 단위로 올려서 표현한다.

async function getPosts() {
  'use cache'
  cacheLife('hours')
  cacheTag('posts')

  const res = await fetch('https://api.example.com/posts')

  return res.json()
}

차이는 작아 보이지만 읽는 위치가 달라진다.
fetch 한 줄의 옵션을 해석하기보다, 함수 전체가 어떤 수명과 태그를 가지는 캐시인지 먼저 보게 된다.

적용 전 확인할 점

Cache Components를 켜면 uncached dynamic data가 개발 중 오류로 드러날 수 있다.
이 오류는 캐시해야 하는 데이터에는 use cache를 붙이고, 요청 시점 데이터가 필요한 영역은 Suspense로 감싸라는 방향을 알려준다.

또 하나는 저장 위치다.
공식 문서 기준 use cache의 기본 저장은 in-memory이고, serverless instance가 사라지면 캐시도 사라질 수 있다.
인스턴스 종료 후에도 유지되는 캐시가 필요하다면 use cache: remote나 cache handler 구성을 검토해야 한다.
새 배포 이후에는 durable storage를 쓰더라도 캐시 값이 다시 계산될 수 있다는 점도 염두에 둔다.

정리

Next.js 16의 Cache Components는 캐싱을 더 명시적인 단위로 옮긴다.
route segment config나 fetch 옵션에 흩어져 있던 캐시 정책을 use cache, cacheLife, cacheTag 중심으로 정리하는 흐름이다.
처음에는 설정이 하나 더 늘어난 것처럼 보이지만, 캐시 범위와 수명이 코드에서 바로 보인다는 점이 장점이다.

참고 자료

관련 포스트
Next.js App Router에서 검색 날짜와 사이트맵 관리하기 thumbnail
Next.js App Router에서 검색 날짜와 사이트맵 관리하기
Next.js App Router 블로그에서 사용자에게 보이는 날짜, Open Graph modifiedTime, JSON-LD dateModified, sitemap lastModified를 같은 기준으로 연결하는 방법을 정리합니다.
Next.js App Router에서 레이아웃 사용하기 thumbnail
Next.js App Router에서 레이아웃 사용하기
Next.js App Router에서 app/layout.tsx, page.tsx, 중첩 layout을 사용해 공통 UI를 구성하는 방법을 정리합니다. Pages Router 시절의 수동 Layout 컴포넌트 패턴과 어떤 점이 다른지도 함께 봅니다.
next image blurDataURL 직접 부여하기 thumbnail
next image blurDataURL 직접 부여하기
Next.js의 Image 컴포넌트에서 blur placeholder를 사용할 때 자동 생성되는 경우와 직접 blurDataURL을 만들어야 하는 경우를 정리합니다. 정적 import, public 경로, 원격 이미지, 빌드 시점 생성 전략을 함께 다룹니다.
Next.js에서 path alias 설정하기 (feat. @/components) thumbnail
Next.js에서 path alias 설정하기 (feat. @/components)
오늘은 Next.js에서 import 시 복잡한 relative path 대신 absolute path 사용을 위한 설정법을 알아봅시다. file path를 상대경로로 지정하다보면 유지보수면에서도 복잡하고, path를 지정할 때마다 경로가 헷갈려서 발생하는 오류는 덤입니다. 이럴 때 path에 대한 alias를 설정하면 코드는 확 깔끔해질 것입니다.  아래 예제를 통해서 따라해봅시다.
Next.js에서 레이아웃 사용하기 thumbnail
Next.js에서 레이아웃 사용하기
홈페이지를 구성할 때, 우리는 대체로 네비게이션과 푸터, 플로팅 버튼 등을 포함합니다. 개발단계에서 이들 컴포넌트를 페이지마다 일일이 import하는 것은 매우 비효율적인 일입니다. 만약 이들처럼 대부분의 페이지에서 보여줘야될 내용이 있다면, 레이아웃 컴포넌트를 통해 손쉽게 유지보수할 수 있을 것입니다.