Next.js 16 릴리즈: 캐싱, 드디어 명시적으로 바뀌다

2026. 1. 22. 17:40·Frontend Development
728x90
반응형

v15에서 "왜 자꾸 캐싱 안 돼?"라고 당황했던 개발자라면, v16의 변화가 반가울 것이다.


들어가며

2025년 10월 21일, Next.js 16이 정식 출시됐다.

릴리즈 노트를 읽다가 눈이 멈춘 부분이 있었다. revalidateTag() 시그니처 변경. 이거 기존 코드 전부 깨지는 거 아닌가? 확인해보니 맞았다. 그것도 두 번째 인자가 필수로 바뀌는 Breaking Change였다.

단순히 API 하나 바뀐 게 아니다. v14 → v15 → v16으로 이어지는 캐싱 철학의 변화가 이번 버전에서 완성됐다. 이 흐름을 이해하지 않으면 마이그레이션할 때 "왜 이렇게 바꿨지?"라는 의문만 남는다.

정리해봤다.


버전별 캐싱 정책의 변화

먼저 세 버전의 캐싱 정책을 한눈에 보자.

버전 캐싱 기본값 개발자 반응
v14 Cached by Default "왜 자꾸 캐싱돼?"
v15 Uncached by Default "왜 갑자기 캐싱 안 돼?"
v16 Explicit "use cache" "아, 내가 명시하면 되는구나"

마치 롤러코스터다. v14에서 과도하게 캐싱하다가, v15에서 갑자기 캐싱을 끄고, v16에서 "너희가 직접 정해라"로 결론 난 것이다.


v14 → v15: "과잉 친절한 집사" 해고

v14의 문제: 시키지도 않은 캐싱

v14의 App Router는 모든 걸 캐싱하려고 했다. fetch 요청, GET 핸들러, 페이지 데이터까지.

// v14에서 이 코드는 자동으로 캐싱됨
async function getUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

문제는 이게 의도치 않은 동작을 만들었다는 것이다.

사용자 A가 프로필 수정
    ↓
DB에는 새 데이터 저장됨
    ↓
페이지 새로고침
    ↓
??? 여전히 옛날 데이터가 보임 ???
    ↓
"아... 캐싱 때문이구나"
    ↓
revalidate 옵션 찾아서 추가

이런 경험 한 번쯤 있지 않은가?

v15의 해결책: 일단 다 끄자

v15는 과감하게 기본값을 뒤집었다.

// v15부터 기본값이 no-store
const res = await fetch(`/api/users/${id}`);
// ↑ 캐싱 안 됨

// 캐싱하려면 명시적으로
const res = await fetch(`/api/users/${id}`, { 
  cache: 'force-cache' 
});

그리고 동기 API들이 비동기로 바뀌었다.

// v14
const cookieStore = cookies();

// v15
const cookieStore = await cookies();

v16의 접근: "네가 직접 말해"

v16은 한 발 더 나아갔다. "use cache" 디렉티브를 도입해서 캐싱을 완전히 선언적으로 만들었다.

Cache Components 활성화

// next.config.ts
const nextConfig = {
  cacheComponents: true,
};

export default nextConfig;

사용 방식

┌─────────────────────────────────────────────────┐
│                                                 │
│  v14: "기본적으로 캐싱할게. 싫으면 말해"         │
│       → 개발자: "왜 자꾸 캐싱돼?"               │
│                                                 │
│  v15: "기본적으로 캐싱 안 할게. 필요하면 말해"   │
│       → 개발자: "매번 옵션 넣기 귀찮은데..."    │
│                                                 │
│  v16: "캐싱할 곳에 use cache 써. 거기만 캐싱함" │
│       → 개발자: "오, 명확하네"                  │
│                                                 │
└─────────────────────────────────────────────────┘

이게 PPR(Partial Prerendering)의 완성형이다. 2023년에 "정적 셸은 즉시, 동적 콘텐츠는 스트리밍"이라는 개념으로 시작했는데, v16에서 프로그래밍 모델로 완성된 것이다.


새로운 Caching API 삼총사

v16에서는 캐시 제어 API도 정비됐다.

revalidateTag() - 시그니처가 바뀌었다

이게 Breaking Change다. 기존 코드 전부 수정해야 한다.

import { revalidateTag } from 'next/cache';

// ❌ v15 이전 - 더 이상 안 됨
revalidateTag('blog-posts');

// ✅ v16 - 두 번째 인자 필수
revalidateTag('blog-posts', 'max');
revalidateTag('products', { expire: 3600 });

두 번째 인자는 cacheLife 프로필이다. 'max', 'hours', 'days' 같은 빌트인 값을 쓰거나, { expire: number }로 직접 지정한다.

updateTag() - 새로 추가됨

Server Actions 전용. 즉시 갱신이 필요할 때 쓴다.

'use server';

import { updateTag } from 'next/cache';

export async function updateUserProfile(userId: string, data: Profile) {
  await db.users.update(userId, data);

  // 캐시 만료 + 즉시 새 데이터 로드
  updateTag(`user-${userId}`);
}

revalidateTag()와 차이점:

  • revalidateTag(): SWR 방식 (일단 캐시 보여주고 백그라운드에서 갱신)
  • updateTag(): 즉시 갱신 (사용자가 바로 변경 확인 가능)

refresh() - 라우터 새로고침 (캐시된 건 유지)

'use server';

import { refresh } from 'next/cache';

export async function markNotificationAsRead(id: string) {
  await db.notifications.markAsRead(id);

  // 현재 라우트 다시 렌더링 트리거
  refresh();
}

알림 카운트처럼 fetch 시 no-store로 설정한 데이터만 새로 받아온다. 캐싱된 컴포넌트나 데이터는 그대로 유지된다.


Turbopack: 이제 진짜 기본값

Turbopack이 드디어 개발과 빌드 모두에서 기본이 됐다.

체감 성능

항목 개선폭
Fast Refresh 최대 10배
프로덕션 빌드 2~5배

Webpack으로 돌아가고 싶다면?

next dev --webpack
next build --webpack

커스텀 Webpack 설정이 복잡하면 당분간 이렇게 쓸 수 있다.

File System Caching (Beta)

// next.config.ts
const nextConfig = {
  experimental: {
    turbopackFileSystemCacheForDev: true,
  },
};

컴파일 결과를 디스크에 저장해서 재시작 시 컴파일 시간을 단축한다. 모노레포 같은 대규모 프로젝트에서 체감된다.


middleware.ts → proxy.ts

미들웨어 파일명이 바뀌었다.

// proxy.ts (구 middleware.ts)
export default function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}

왜 바꿨을까?

┌─────────────────────────────────────────────────┐
│                                                 │
│  "middleware"라는 이름이 모호했다               │
│                                                 │
│  실제 역할: 네트워크 경계에서 요청 가로채기     │
│  새 이름: proxy                                │
│  런타임: Node.js (Edge 아님)                   │
│                                                 │
└─────────────────────────────────────────────────┘

middleware.ts는 Edge 런타임 케이스를 위해 아직 동작하지만, deprecated다.


Enhanced Routing: 프리페칭이 똑똑해졌다

Layout Deduplication

50개의 상품 링크가 있는 페이지를 생각해보자.

v15까지:
  Link 1 프리페칭 → 레이아웃 + 페이지 데이터
  Link 2 프리페칭 → 레이아웃 + 페이지 데이터
  ...
  Link 50 프리페칭 → 레이아웃 + 페이지 데이터

  결과: 레이아웃 50번 다운로드 (중복!)

v16:
  Link 1 프리페칭 → 레이아웃 + 페이지 데이터
  Link 2 프리페칭 → 페이지 데이터만 (레이아웃은 캐시됨)
  ...
  Link 50 프리페칭 → 페이지 데이터만

  결과: 레이아웃 1번만 다운로드

Incremental Prefetching

  • 이미 캐시된 부분은 건너뜀
  • 뷰포트를 벗어나면 프리페칭 취소
  • hover 시 우선순위 상승
  • 데이터 무효화되면 자동 재프리페칭

개별 요청 수는 늘어날 수 있지만, 총 전송량은 크게 줄어든다.


React 19.2 Canary 기능들

v16은 React 최신 Canary를 사용한다.

View Transitions

트랜지션/네비게이션 시 요소에 애니메이션 적용.

useEffectEvent()

Effect에서 비반응형 로직을 분리.

<Activity/>

// display: none으로 숨기면서 상태 유지
<Activity mode="hidden">
  <HeavyComponent />
</Activity>

탭 전환 같은 시나리오에서 유용하다.


Breaking Changes 체크리스트

버전 요구사항

항목 최소 버전
Node.js 20.9+ (18 지원 종료)
TypeScript 5.1.0+
Chrome/Edge 111+
Firefox 111+
Safari 16.4+

제거된 것들

제거됨 대체 방법
AMP 지원 완전 삭제됨
next lint Biome 또는 ESLint CLI 직접 사용
serverRuntimeConfig .env 환경변수
experimental.ppr cacheComponents
동기 cookies(), headers() await 필수

동작 변경

항목 Before After
images.minimumCacheTTL 60초 4시간
images.qualities [1..100] [75]
Parallel routes default.js 선택 default.js 필수
revalidateTag() 인자 1개 인자 2개 필수

마이그레이션 가이드

자동 마이그레이션

npx @next/codemod@canary upgrade latest

대부분 자동 처리된다.

수동으로 확인할 것

1. revalidateTag() 전부 수정

// Before
revalidateTag('posts');

// After
revalidateTag('posts', 'max');

2. Parallel Routes에 default.js 추가

없으면 빌드가 실패한다.

// app/@modal/default.js
export default function Default() {
  return null;
}

3. Node.js 버전 확인

node -v  # 20.9.0 이상인지 확인

정리: 버전별 비교

구분 v14 v15 v16
캐싱 모델 Cached by Default Uncached by Default Explicit "use cache"
번들러 Webpack Turbopack (Dev) Turbopack (Full)
데이터 API 동기 비동기 필수 비동기 + 새 API
React 18 19 19.2 Canary
미들웨어 middleware.ts middleware.ts proxy.ts

마치며

Next.js의 캐싱 정책 변화는 "좋은 기본값이 뭔가?"에 대한 Vercel의 고민을 보여준다.

v14: "다 캐싱하면 빠르겠지" → 예측 불가능한 동작
v15: "일단 다 끄자" → 매번 명시하기 귀찮음
v16: "필요한 곳에 선언해라" → 명확하고 예측 가능

결국 "명시적인 게 암묵적인 것보다 낫다"는 결론에 도달한 것 같다.

마이그레이션 시 revalidateTag() 시그니처 변경과 Parallel Routes의 default.js 필수화를 특히 주의하자. 나머지는 codemod가 대부분 처리해준다.

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'Frontend Development' 카테고리의 다른 글

React의 Error Boundary와 비동기 오류 처리  (0) 2025.10.13
React 리렌더링(Re-rendering): Trigger → Render → Commit  (1) 2025.10.03
실무에서 꼭 알아야 할 JWT 저장소 보안 패턴과 공격 탐지 방법  (0) 2025.09.13
Next.js SSR 페이지 풀 페이지 캐싱  (0) 2025.09.08
useEffect에서 setInterval이 상태를 못 따라오는 이유 (stale closure)  (0) 2025.09.07
'Frontend Development' 카테고리의 다른 글
  • React의 Error Boundary와 비동기 오류 처리
  • React 리렌더링(Re-rendering): Trigger → Render → Commit
  • 실무에서 꼭 알아야 할 JWT 저장소 보안 패턴과 공격 탐지 방법
  • Next.js SSR 페이지 풀 페이지 캐싱
Kun Woo Kim
Kun Woo Kim
안녕하세요, 김건우입니다! 웹과 앱 개발에 열정적인 전문가로, React, TypeScript, Next.js, Node.js, Express, Flutter 등을 활용한 프로젝트를 다룹니다. 제 블로그에서는 개발 여정, 기술 분석, 실용적 코딩 팁을 공유합니다. 창의적인 솔루션을 실제로 적용하는 과정의 통찰도 나눌 예정이니, 궁금한 점이나 상담은 언제든 환영합니다.
  • Kun Woo Kim
    WhiteMouseDev
    김건우
  • 깃허브
    포트폴리오
    velog
  • 전체
    오늘
    어제
  • 공지사항

    • [인사말] 이제 티스토리에서도 만나요! WhiteMouse⋯
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 분류 전체보기 (143)
      • Frontend Development (64)
      • Backend Development (27)
      • AI · ML (3)
        • Computer Vision (3)
      • Algorithm (35)
        • 백준 (11)
        • 프로그래머스 (18)
        • 알고리즘 (5)
      • Infra (1)
      • 자료구조 (4)
      • Language (6)
        • JavaScript (6)
  • 링크

    • Github
    • Portfolio
    • Velog
  • 인기 글

  • 태그

    transformer
    객체탐지
    CNN
    rt-detr
    컴퓨터비전
    YOLO
    tailwindcss
    모델비교
    Object Detection
    frontend development
    AI
    딥러닝
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Kun Woo Kim
Next.js 16 릴리즈: 캐싱, 드디어 명시적으로 바뀌다
상단으로

티스토리툴바