Next.js 16 새 기능 변경사항 5가지 총정리 — proxy.ts·Cache·Turbopack

목차

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

이 코드가 뭘까? Next.js 16 새 기능 변경사항 중 가장 먼저 눈에 띄는 부분이다. 기존 middleware.tsproxy.ts로 이름이 바뀌었다. 파일명만 바뀐 게 아니라 런타임도 달라졌고, 내보내는 함수명도 변경되었다. Next.js 15까지 쓰던 코드가 16에서 그대로 동작하지 않는 breaking change가 여러 개 포함되어 있으므로, 업그레이드 전에 반드시 확인이 필요하다.

이 글에서는 Next.js 16의 주요 breaking change 3가지와 신규 API 2가지를 코드 예제 중심으로 정리한다.

Next.js 16 proxy.ts: middleware에서 바뀐 것들

proxy-ts-middleware-change

Next.js 16에서 가장 큰 변경은 middleware.tsproxy.ts로 이름이 변경된 것이다. 단순 리네임이 아니라 런타임 자체가 달라진 셈이다.

변경 항목을 구체적으로 나열하면:

  • 파일명: middleware.tsproxy.ts
  • 내보내는 함수: middlewareproxy
  • 런타임: Edge Runtime → Node.js Runtime
  • 설정 키: skipMiddlewareUrlNormalizeskipProxyUrlNormalize

기존 middleware.ts는 Edge 런타임 전용으로 deprecated 상태가 되었다. Next.js 16 릴리스 노트에서 이 변경의 배경을 확인할 수 있다.

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

이 코드에서 주목할 점은 함수명이 proxy라는 것이다. 기존에 export function middleware()로 내보내던 방식과 호환되지 않는다. next.config.ts에서 URL 정규화 스킵 옵션을 쓰고 있었다면 키 이름도 함께 바꿔야 하는 구조다.

middleware.ts 호환성 주의
Next.js 16에서 middleware.ts는 Edge 런타임 전용 deprecated 상태다. Node.js 런타임 기능(DB 직접 접근, fs 모듈 등)을 쓰려면 반드시 proxy.ts로 전환해야 한다.
마이그레이션 자체는 파일명 변경과 함수명 변경 두 단계로 끝나지만, Edge 런타임에 의존하는 라이브러리를 쓰고 있었다면 Node.js 런타임 호환성도 같이 확인해야 하는 점이 까다롭다. 공식 문서에도 proxy.ts의 Edge 런타임 대안 가이드는 아직 미완성 상태이며 추후 마이너 릴리스에서 보완될 예정이라고 명시되어 있다.

proxy.ts 마이그레이션 순서

실제로 전환할 때는 아래 순서를 따르면 된다:

  1. middleware.tsproxy.ts로 파일명 변경
  2. export function middlewareexport default function proxy로 수정
  3. next.config.ts에서 skipMiddlewareUrlNormalizeskipProxyUrlNormalize로 변경
  4. Edge 전용 API 사용 여부를 확인하고, Node.js 런타임에서 동작하는지 테스트

Next.js 16 비동기 전용 Dynamic API 변경사항

Next.js 16에서 두 번째로 큰 breaking change는 Dynamic API의 동기 접근이 완전 제거된 것이다. cookies(), headers(), draftMode(), params, searchParams 모두 await로만 접근할 수 있게 되었다.

Next.js 15에서는 동기 접근 시 deprecation 경고만 나왔지만, 16에서는 에러가 발생한다. 기존 코드가 상당히 깨질 수 있는 부분이다.

// page.tsx
export default async function Page(props: PageProps<'/blog/[slug]'>) {
  const { slug } = await props.params
  const query = await props.searchParams
  return <h1>Blog Post: {slug}</h1>
}

위 코드에서 props.paramsprops.searchParams 모두 await를 붙여야 한다. sitemapid 파라미터와 opengraph-imageparamsPromise로 변경된 것이 포인트다.

APINext.js 15Next.js 16
cookies()동기 접근 가능 (경고)await 필수
headers()동기 접근 가능 (경고)await 필수
params동기 접근 가능 (경고)await 필수
searchParams동기 접근 가능 (경고)await 필수
draftMode()동기 접근 가능 (경고)await 필수
sitemap id동기Promise
opengraph-image params동기Promise
빠른 마이그레이션 팁
프로젝트에서 `cookies()`, `headers()`, `params`, `searchParams`를 `await` 없이 쓰는 곳을 찾으려면 TypeScript strict 모드를 켜고 빌드하면 된다. 타입 에러로 모든 위치가 잡히는 편이다.
이 변경의 영향 범위가 넓은 이유는, 레이아웃과 페이지 컴포넌트뿐 아니라 `generateMetadata`, `generateStaticParams` 같은 함수에서도 동일하게 적용되기 때문이다. [Next.js 16 업그레이드 가이드](https://nextjs.org/docs/app/guides/upgrading/version-16)에서 전체 영향 범위를 상세히 다루고 있으니, 마이그레이션 전에 해당 문서를 통째로 읽는 것을 권장한다.

비동기 전환 시 주의할 패턴

기존에 params를 구조 분해 할당으로 바로 받던 패턴이 동작하지 않게 된다는 점이 함정이다:

// ❌ Next.js 16에서 동작하지 않음
export default function Page({ params: { slug } }) {
  return <h1>{slug}</h1>
}
// ✅ Next.js 16 방식
export default async function Page(props: PageProps<'/blog/[slug]'>) {
  const { slug } = await props.params
  return <h1>{slug}</h1>
}

컴포넌트 함수에 async를 붙여야 하므로, 클라이언트 컴포넌트에서는 이 패턴을 직접 쓸 수 없다. 서버 컴포넌트에서 await로 값을 꺼낸 뒤 클라이언트 컴포넌트에 props로 전달하는 구조가 필요하게 되는 것이다.

Turbopack 기본 번들러 전환과 Next.js 16 빌드 성능

turbopack-bundler-performance

Turbopack이 안정화되어 Next.js 16의 기본 번들러가 되었다. next devnext build 모두 Turbopack을 기본 사용하며, 최대 10배 빠른 Fast Refresh와 2~5배 빠른 프로덕션 빌드를 제공한다는 것이 공식 발표 내용이다.

이것도 breaking change에 해당하는 이유가 있다. experimental.turbopack 설정이 최상위 turbopack으로 이동했기 때문이다.

// next.config.ts (Next.js 16)
const nextConfig: NextConfig = {
  turbopack: {
    // options
  },
};

export default nextConfig;

기존에 experimental.turbopack 아래에 옵션을 넣고 있었다면 설정 경로를 바꿔야 하는 구조다. webpack을 계속 쓰고 싶은 경우 --webpack 플래그로 opt-out이 가능하다.

항목webpack (opt-out)Turbopack (기본)
Fast Refresh기준선최대 10배 빠름
프로덕션 빌드기준선2~5배 빠름
설정 위치별도 플래그 필요 없음turbopack: {} (최상위)
활성화--webpack 플래그기본값
Turbopack 설정 이동 확인
`next.config.ts`에서 `experimental.turbopack`을 사용 중이었다면 최상위 `turbopack`으로 옮겨야 한다. 설정 경로가 그대로면 무시되므로 빌드 옵션이 적용되지 않는 문제가 생길 수 있다.
### webpack에서 Turbopack으로 전환 시 체크포인트

Turbopack은 webpack의 모든 플러그인을 지원하지 않는다. 커스텀 webpack 설정이 많은 프로젝트에서는 다음 항목을 점검해야 하는 편이다:

  • webpack 콜백 함수로 직접 로더를 추가한 경우 → Turbopack 호환 로더 확인 필요
  • next-bundle-analyzer 같은 webpack 전용 플러그인 → Turbopack 대안이 있는지 확인
  • 커스텀 module.rules → Turbopack의 rules 설정으로 매핑 필요

규모가 큰 프로젝트에서는 한 번에 전환하기보다, 개발 환경(next dev)에서 먼저 Turbopack을 테스트하고, 프로덕션 빌드는 --webpack으로 유지하다가 점진적으로 이동하는 전략이 안전하다.

Next.js 16 Cache Components: “use cache” 디렉티브

Next.js 16은 Cache Components를 도입하면서 캐싱 모델을 근본적으로 바꿨다. "use cache" 디렉티브를 사용해 페이지·컴포넌트·함수를 명시적으로 캐싱하며, 컴파일러가 자동으로 캐시 키를 생성하는 방식이다.

기존에 쓰던 experimental.pprexperimental.dynamicIO 플래그는 제거되었고, cacheComponents 설정으로 대체된 것이 핵심 변화다.

const nextConfig = {
  cacheComponents: true,
};

export default nextConfig;

cacheComponents: true를 설정하면 "use cache" 디렉티브를 인식하게 된다. 이 디렉티브는 "use client""use server"와 같은 위치에 선언하면 되는 구조다.

Cache Components의 동작 원리를 정리하면 이렇게 되는 셈이다:

  1. 컴포넌트나 함수 상단에 "use cache" 선언
  2. 컴파일러가 해당 유닛의 입력(props, 인자)을 분석해 캐시 키를 자동 생성
  3. 같은 입력이 들어오면 캐시된 결과를 반환
  4. 캐시 무효화는 updateTag() 또는 revalidateTag()로 처리
experimental 플래그 제거 주의
`experimental.ppr`과 `experimental.dynamicIO`를 next.config에서 쓰고 있었다면 제거하고 `cacheComponents: true`로 교체해야 한다. 기존 플래그가 남아있으면 설정 유효성 검사에서 경고가 발생할 수 있다.
다만 `”use cache”` 실전 적용 패턴의 심화 예제는 공식 문서에도 아직 충분하지 않은 상태다. 기본적인 페이지·컴포넌트 단위 캐싱은 문서에 나와 있지만, 복잡한 데이터 의존성을 가진 경우의 캐시 키 생성 전략이나 중첩 캐시 무효화 패턴은 공식 문서에 명시되어 있지 않다.

Cache Components 활성화 체크리스트

Cache Components 적용 전 확인 항목:

  • next.config.tscacheComponents: true 추가 여부
  • experimental.ppr, experimental.dynamicIO 제거 여부
  • "use cache" 디렉티브를 쓸 컴포넌트가 순수 함수(같은 입력 → 같은 출력)인지 확인
  • 캐시 무효화 전략 수립 (updateTag vs revalidateTag 선택)

Next.js 16 updateTag와 revalidateTag 캐시 갱신 비교

Next.js 16에서 캐시 갱신 API도 큰 변화가 있다. updateTag()는 Server Actions 전용 신규 API로, read-your-writes 시맨틱을 제공하는 것이 가장 큰 특징이다. 캐시를 만료시키고 같은 요청 내에서 즉시 새 데이터를 읽을 수 있다.

'use server';
import { updateTag } from 'next/cache';

export async function updateUserProfile(userId: string, profile: Profile) {
  await db.users.update(userId, profile);
  updateTag(`user-${userId}`);
}

revalidateTag()도 변경되었다. 두 번째 인자로 cacheLife 프로필(예: 'max')이 필수가 된 것이다. 기존처럼 태그만 넘기면 타입 에러가 발생하게 된다.

refresh()는 캐시되지 않은 데이터만 새로고침하는 Server Actions 전용 API로 별도 추가되었다.

API용도캐시 데이터비캐시 데이터같은 요청 내 반영
updateTag()캐시 즉시 갱신✅ 만료+즉시 반영✅ (read-your-writes)
revalidateTag()캐시 재검증 예약✅ 다음 요청에 반영
refresh()비캐시 새로고침

updateTag()를 쓸지 revalidateTag()를 쓸지는 사용자 경험 요구사항에 따라 갈리는 편이다. 프로필 수정 후 즉시 변경된 값을 보여줘야 하는 경우라면 updateTag()가 적합하고, 백그라운드에서 캐시가 갱신되어도 괜찮은 경우라면 revalidateTag()로 충분하다.

updateTag 사용 시나리오
사용자가 폼을 제출하고 바로 결과 페이지를 볼 때 이전 캐시가 보이는 문제가 있었다면, updateTag()로 해결 가능하다. Server Action 내에서 DB 업데이트 직후 호출하면 같은 응답에 새 데이터가 반영되기 때문이다.
## Next.js 16 마이그레이션 체크리스트

Next.js 16 breaking change 3가지와 신규 API 2가지의 마이그레이션 순서를 정리했다. breaking change부터 처리하고, 신규 API는 이후에 점진 적용하는 전략이 안전하다.

1단계: Breaking Change 처리 (필수)

[마이그레이션 체크리스트]
├── middleware.ts → proxy.ts
│   ├── 파일명 변경
│   ├── export function middleware → export default function proxy
│   └── skipMiddlewareUrlNormalize → skipProxyUrlNormalize
├── 비동기 API 전환
│   ├── cookies() → await cookies()
│   ├── headers() → await headers()
│   ├── params → await props.params
│   ├── searchParams → await props.searchParams
│   └── draftMode() → await draftMode()
└── Turbopack 설정
    ├── experimental.turbopack → turbopack (최상위)
    └── webpack 의존성 호환 테스트

2단계: 신규 API 적용 (선택)

  • cacheComponents: true 활성화 후 "use cache" 디렉티브 적용
  • revalidateTag() 호출부에 두 번째 인자 cacheLife 프로필 추가
  • 즉시 반영이 필요한 Server Action에 updateTag() 도입
  • refresh() API로 비캐시 데이터 새로고침 로직 분리

한국어 공식 마이그레이션 가이드는 아직 제공되지 않는 상태다. 영문 업그레이드 가이드를 기준으로 작업해야 한다.

Next.js 16 업그레이드 이후 다음 단계

Next.js 16 새 기능 변경사항의 핵심은 세 가지 breaking change(proxy.ts 전환, 비동기 API 강제, Turbopack 기본화)와 두 가지 신규 API(Cache Components, updateTag)로 요약되는 셈이다.

마이그레이션 완료 후에는 세 영역을 추가로 점검해야 한다. Next.js 16 Cache Components를 활용한 세분화된 캐시 전략 설계가 첫 번째다. 페이지 단위가 아니라 컴포넌트·함수 단위로 캐시를 제어할 수 있게 되었으므로, 데이터 갱신 빈도에 따라 캐시 경계를 어디에 둘지 설계하는 작업이 중요해진다.

Next.js Turbopack 기본 번들러 환경에서의 빌드 파이프라인 최적화도 검토 대상이다. webpack 플러그인 생태계에 의존하던 부분을 Turbopack 네이티브 방식으로 전환하면 빌드 속도 이점을 최대로 끌어낼 수 있게 된다. 그리고 React 19.2와 Next.js 통합, 특히 View Transitions 같은 기능은 실전 예제가 아직 부족한 상태이므로 공식 문서 업데이트를 지켜볼 필요가 있다.

Next.js 16 비동기 params searchParams 패턴에 익숙해지면, 서버 컴포넌트와 클라이언트 컴포넌트 간 데이터 흐름 설계도 자연스럽게 정리될 것이다. Next.js 16 proxy.ts middleware 변경까지 적용하고 나면 Next.js 16 업그레이드의 핵심 작업은 마무리된다.

관련 글

이 글 공유하기