2022년 발표 이후로 Turbopack의 성숙도를 지켜봤고, Next.js 14부터 개발 모드에서 실행했으며, CI에서 "turbo" 플래그를 조심스레 지켜봤습니다. 2025년 초에 Next.js 16이 Turbopack을 기본 프로덕션 번들러로 탑재하면서 마이그레이션을 더 이상 미룰 수 없게 되었습니다. Next.js 15에서 업그레이드가 필요한 세 개의 클라이언트 프로젝트가 있었고, 저는 모든 주요 변경 사항, 직면한 문제점, 그리고 우리가 마주한 성능 수치들을 안내해드리겠습니다.

이것은 릴리스 노트의 재해석이 아닙니다. 실제 복잡도를 가진 실제 코드베이스에서 Turbopack으로 next build를 실행했을 때 실제로 일어난 일입니다.

목차

Next.js 16 Turbopack 프로덕션 빌드: 변경 사항 및 마이그레이션 방법

Next.js 16이 중요한 이유

Next.js 16은 단순한 버전 업이 아닙니다. 프레임워크가 Pages에서 App Router로 이동한 이후로 빌드 인프라에 가장 중요한 변화를 나타냅니다. 주요 기능은 명확합니다: Turbopack이 개발 및 프로덕션 빌드 모두에서 webpack을 대체합니다.

하지만 더 많은 것들이 있습니다. Next.js 16은 또한 다음을 함께 제공합니다:

  • React 19 최소 지원 버전 — React 18 호환성은 더 이상 없음
  • 향상된 스트리밍 및 부분 사전 렌더링 성숙도
  • 새로운 캐싱 기본값 (Next.js 15 캐싱 논쟁으로부터 배운 점)
  • 완전히 적용된 비동기 요청 APIcookies(), headers(), params는 모두 레거시 동기 지원이 없는 비동기가 됨
  • Node.js 20 최소 요구 사항 — Node 18 지원은 제거됨

Next.js 개발을 하는 에이전시 같은 우리에게는 이런 종류의 릴리스가 모든 것을 건드립니다. 버전 번호를 올리고 끝낼 수는 없습니다.

Turbopack의 프로덕션 변경 사항

구체적으로 살펴봅시다. Next.js 14 및 15 동안 Turbopack은 --turbo 플래그로 next dev에 사용 가능했습니다. 프로덕션 빌드는 여전히 webpack을 사용했습니다. Next.js 15.3은 next build를 위한 실험적 --turbopack 플래그를 도입했고, 16이 출시될 무렵에는 기본값이 되었습니다.

다음은 Turbopack이 webpack과 비교하여 프로덕션 빌드를 처리하는 방식의 기본적인 차이점입니다:

증분 컴파일 아키텍처

Webpack은 매번 전체 의존성 그래프를 처리합니다. Turbopack은 Turbo 엔진(Rust로 작성됨)에 기반한 함수 수준 캐싱 시스템을 사용합니다. 실제로 이는 후속 빌드가 실제로 변경된 것만 다시 컴파일한다는 의미입니다. 첫 번째 빌드는 극적으로 빠르지 않을 수 있지만, 두 번째, 세 번째, 그리고 열 번째 빌드는 그렇습니다.

트리 쉐이킹 개선

Turbopack의 트리 쉐이킹은 webpack의 트리 쉐이킹보다 더 세분화된 수준에서 작동합니다. 우리는 코드 변경 없이 평균적으로 클라이언트 번들이 8-15% 작아진 것을 발견했습니다. 가장 큰 성과는 배럴 파일 처리에서 나왔습니다 — Turbopack은 인덱스 파일의 사용하지 않는 재내보내기를 제거하는 데 진정으로 더 나닙니다.

모듈 해석

Turbopack은 모듈을 다르게 해석합니다. 더 빠르지만 더 엄격합니다. webpack이 조용히 해석하고 있던 느슨한 임포트 경로가 있었다면(특정 엣지 케이스에서 파일 확장자 누락, 또는 macOS에서는 대소문자를 구분하지 않지만 Linux에서 끊기는 경로), Turbopack은 이를 잡습니다. 이것은 우리의 마이그레이션 문제의 약 30%를 유발했습니다.

코드 분할 전략

청크 분할 알고리즘은 새로운 것입니다. Turbopack은 기본적으로 더 많고 작은 청크를 만듭니다. 이것은 일반적으로 HTTP/2가 있는 최신 브라우저의 로딩 성능을 향상시키지만, 총 요청 수를 증가시킬 수 있습니다. 우리는 청크 개수가 약 40% 증가하는 동안 총 번들 크기는 감소한 것을 보았습니다.

SWC는 이제 필수

Babel 구성에 여전히 매달려 있었다면, 이제 없습니다. Turbopack은 변환에만 SWC를 사용합니다. 이는 이미 진행 방향이었지만, Next.js 16은 Babel로의 모든 폴백을 완전히 제거합니다.

빌드 성능 벤치마크

세 개의 마이그레이션 프로젝트로부터의 실제 수치입니다. 이것들은 합성 벤치마크가 아닙니다 — GitHub Actions(Ubuntu, 4 vCPU, 16GB RAM 러너)에서 실행되는 CI 파이프라인의 실제 클라이언트 애플리케이션입니다.

메트릭 프로젝트 A (전자상거래) 프로젝트 B (SaaS 대시보드) 프로젝트 C (콘텐츠 사이트)
페이지/라우트 847 124 2,340
webpack 빌드 (Next 15) 4m 32s 1m 48s 6m 15s
Turbopack 빌드 (Next 16, 콜드) 3m 10s 1m 22s 4m 44s
Turbopack 빌드 (Next 16, 웜 캐시) 1m 05s 28s 1m 52s
번들 크기 변화 -12% -8% -14%
JS 첫 로드 (홈페이지) -18KB -7KB -22KB
빌드 메모리 피크 3.8GB → 2.9GB 1.6GB → 1.2GB 4.2GB → 3.1GB

웜 캐시 수치가 실제 이야기입니다. Turbopack이 프로젝트를 한 번 빌드한 후에는, 증분 재빌드가 극적으로 빨라집니다. 수천 개의 정적으로 생성된 페이지가 있는 헤드리스 CMS를 사용하는 콘텐츠가 풍부한 프로젝트 C의 경우, 6분 이상에서 캐시된 빌드에서 2분 미만으로 가는 것은 의미가 있었습니다. 이것은 CI 분에서 절약되는 실제 비용입니다.

메모리 사용량 개선도 의미 있었습니다. 프로젝트 A는 webpack으로 더 작은 CI 러너에서 가끔 OOM 오류를 맞기도 했습니다. 이 문제는 Turbopack과 함께 사라졌습니다.

Next.js 16 Turbopack 프로덕션 빌드: 변경 사항 및 마이그레이션 방법 - 아키텍처

알아야 할 주요 변경 사항

다음은 마이그레이션 중에 실제로 깨진 모든 것의 실행 목록입니다. Next.js 16 업그레이드 가이드가 이 중 일부를 다루지만, 몇 개는 우리를 놀라게 했습니다.

1. 사용자 정의 Webpack 구성

이것이 큰 것입니다. next.config.jswebpack 키가 있으면 더 이상 작동하지 않습니다. Turbopack은 Next.js 설정의 turbopack 아래에 자체 구성 API를 가지고 있습니다. 모든 것이 1:1 맵핑을 가지는 것은 아닙니다.

// next.config.js — 이전 (webpack을 사용하는 Next.js 15)
module.exports = {
  webpack: (config) => {
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    });
    return config;
  },
};
// next.config.js — 이후 (Turbopack을 사용하는 Next.js 16)
module.exports = {
  turbopack: {
    rules: {
      '*.svg': {
        loaders: ['@svgr/webpack'],
        as: '*.js',
      },
    },
  },
};

2. 동기 요청 API 제거

Next.js 15는 cookies(), headers(), params, searchParams에 대한 동기 접근을 더 이상 권장하지 않았습니다. Next.js 16은 이를 완전히 제거합니다. 그 사용 중단 경고를 무시했다면, 힘든 시간을 보낼 것입니다.

// 이전 — Next.js 16에서 이것은 충돌함
export default function Page({ params }) {
  const { slug } = params;
  return <div>{slug}</div>;
}

// 이후
export default async function Page({ params }) {
  const { slug } = await params;
  return <div>{slug}</div>;
}

이것은 사소해 보이지만 우리 프로젝트 전체에서 200개 이상의 컴포넌트를 건드렸습니다. 대부분을 처리하기 위해 codemod를 작성했습니다 (아래에서 자세히 설명).

3. React 18은 더 이상 지원되지 않음

Next.js 16은 React 19가 필요합니다. React 18로 고정된 의존성이 있으면 업데이트해야 합니다. 대부분의 잘 유지되는 라이브러리는 2025년 중반까지 React 19 지원을 가지고 있었지만, 몇 가지 낙오자를 만났습니다.

4. Node.js 18 제거

최소값은 Node.js 20입니다. Docker 이미지, CI 설정 및 .nvmrc 파일을 업데이트합니다.

5. next/image 변경 사항

onLoadingComplete prop은 완전히 제거되었습니다 (Next.js 14부터 더 이상 권장됨). 대신 onLoad를 사용합니다. 이미지 최적화 파이프라인도 새로운 기본 라이브러리를 사용하므로, 캐시된 최적화 이미지는 첫 번째 요청에서 재생성됩니다.

단계별 마이그레이션 프로세스

다음은 우리가 따른 실제 프로세스입니다. 우리는 먼저 프로젝트 B (가장 작은 것)를 테스트 실행으로 했고, 그 다음 A와 C를 처리했습니다.

단계 1: 의존성 감사

Next.js를 건드리기 전에, 우리는 React 19 및 Node.js 20 호환성에 대해 모든 의존성을 감사했습니다. 우리는 간단한 스크립트를 사용했습니다:

npx npm-check-updates --target latest --filter '/react|next/'

우리는 또한 수동으로 가장 중요한 패키지를 확인했습니다: 헤드리스 CMS SDK, 인증 라이브러리, UI 컴포넌트 라이브러리.

단계 2: Node.js 업데이트

우리는 .nvmrc20.18.0 (당시 최신 LTS)로 업데이트했고, Dockerfile을 업데이트하고, CI 러너를 확인했습니다. 간단하지만 잊기 쉽습니다.

단계 3: 먼저 React 업그레이드

npm install react@19 react-dom@19 @types/react@19 @types/react-dom@19

우리는 계속하기 전에 전체 테스트 스위트를 실행했습니다. React 19는 자체의 주요 변경 사항을 가지고 있습니다 (더 이상 필요하지 않은 forwardRef 제거, ref는 이제 일반 prop, use() 훅은 안정적). 우리는 React 및 Next.js 문제를 동시에 디버깅하지 않도록 격리된 상태에서 이 문제들을 수정했습니다.

단계 4: Next.js Codemod 실행

Next.js는 많은 기계적 변경을 처리하는 업그레이드 codemod를 제공합니다:

npx @next/codemod@latest upgrade

이것은 자동으로 비동기 API 마이그레이션의 약 70%를 처리했습니다. 완벽하지는 않습니다 — 일부 더 복잡한 서버 컴포넌트 패턴으로 어려움을 겪었습니다 — 하지만 수시간을 절약했습니다.

단계 5: Next.js 업그레이드

npm install next@16

단계 6: next.config.js 마이그레이션

이것은 상당한 webpack 커스터마이징이 있었던 프로젝트 A의 가장 시간이 많이 걸리는 단계였습니다. 다음 섹션에서 특정 변환을 다룰 것입니다.

단계 7: 빌드 오류를 반복적으로 수정

우리는 next build를 실행했고 하나씩 오류를 처리했습니다. Turbopack의 오류 메시지는 실제로 대부분의 경우 webpack의 메시지보다 낫습니다 — 더 구체적이고, 더 명확한 파일 경로와 제안이 있습니다.

단계 8: 시각적 회귀 테스팅

우리는 Playwright를 E2E 테스트에 사용하고 시각적 회귀 스위트를 실행했습니다. 우리는 두 가지 문제를 발견했습니다: CSS 순서 차이 (Turbopack은 webpack과 약간 다른 순서로 CSS 임포트를 처리함) 및 올바르게 코드 분할되지 않는 동적 임포트.

단계 9: 성능 검증

우리는 마이그레이션 전후로 Lighthouse 점수와 Core Web Vitals을 비교했습니다. 모든 프로젝트가 개선되었거나 중립적으로 유지되었습니다. 회귀는 없었습니다.

Webpack 설정 변환

이 섹션은 사용자 정의 webpack 설정이 있는 팀을 위한 것입니다. 일반적인 패턴이 Turbopack으로 어떻게 변환되는지는 다음과 같습니다.

사용자 정의 로더

// 사용자 정의 로더에 대한 Turbopack 동등물
module.exports = {
  turbopack: {
    rules: {
      '*.md': {
        loaders: ['raw-loader'],
        as: '*.js',
      },
      '*.graphql': {
        loaders: ['graphql-tag/loader'],
        as: '*.js',
      },
    },
  },
};

모듈 별칭

// 해석 별칭이 유사하게 작동함
module.exports = {
  turbopack: {
    resolveAlias: {
      'old-package': 'new-package',
      // 로컬 파일을 가리킬 수도 있음
      '@legacy/utils': './src/utils/legacy.ts',
    },
  },
};

변환되지 않는 것

일부 webpack 플러그인은 아직 Turbopack 동등물을 가지고 있지 않습니다:

  • webpack.DefinePlugin — next.config.js의 env 또는 직접 환경 변수를 사용합니다
  • BundleAnalyzerPlugin@next/bundle-analyzer 패키지는 v16부터 Turbopack과 작동하지만 출력 형식이 변경되었습니다
  • splitChunks를 통한 사용자 정의 청크 분할 — Turbopack은 이를 자동으로 처리하고 동일한 수준의 제어를 제공하지 않습니다. 솔직히 기본값은 대부분의 프로젝트에 충분합니다.
  • webpack.IgnorePluginresolveAlias를 사용하여 임포트를 빈 모듈로 가리킵니다

타사 패키지 처리

몇몇 패키지가 마이그레이션 중에 문제를 일으켰습니다:

@sentry/nextjs — Turbopack 호환성을 위해 v9+ 필요. 이전 버전은 webpack 내부에 후킹되었습니다. 업그레이드는 간단했지만 설정 변경이 필요했습니다.

next-intl — 최신 버전으로 업데이트한 후 잘 작동했습니다. 플러그인 API가 깔끔하게 적응했습니다.

@vanilla-extract/next-plugin — 이것은 프로젝트 B의 가장 큰 골칫거리였습니다. Vanilla Extract의 webpack 플러그인은 그들의 2.0 릴리스까지 Turbopack 동등물을 가지지 않았습니다. 우리는 이를 기다리거나 대안을 고려해야 했습니다. 우리는 기다렸습니다.

배럴 파일 패키지 — 단일 인덱스 파일에서 수백 개의 컴포넌트를 내보내는 모든 패키지 (아이콘 라이브러리를 보면)는 이제 훨씬 더 적극적으로 트리 쉐이크됩니다. 이것은 좋은 것이지만, 동적으로 참조되는 아이콘이 포함되지 않는 한 가지 경우를 보았습니다. 우리는 문자열 기반 아이콘 조회에서 직접 임포트로 전환했으며, 이것이 어쨌든 더 나은 관행입니다.

CSS 및 Tailwind 고려 사항

Tailwind CSS를 사용하고 있다면 (그리고 우리의 대부분의 프로젝트는), 마이그레이션은 대부분 고통 없습니다. Tailwind v4는 Turbopack과 훌륭하게 작동합니다. 하지만 주의할 몇 가지가 있습니다:

CSS 임포트 순서

Turbopack은 webpack과 다른 결정론적이지만 다른 순서로 CSS 임포트를 처리합니다. 특이성을 위한 임포트 순서에 의존하고 있다면 (그리고 당신이 해서는 안 되지만, 솔직히 우리 모두가 거기에 끝나갑니다), 시각적 차이를 볼 수 있습니다. 우리는 글로벌 재설정이 임포트 순서가 뒤집혔기 때문에 컴포넌트 CSS 모듈에 의해 재정의되는 한 프로젝트가 있었습니다.

수정은 CSS에서 명시적인 @layer 사용이었고, 우리는 이미 이 모든 시간을 하고 있어야 했습니다.

CSS 모듈

CSS 모듈은 동일하게 작동합니다. 변경이 필요 없습니다. 생성된 클래스 이름은 다르게 보입니다 (더 짧음, 실제로), 하지만 생성된 클래스 이름을 테스트에서 대상으로 하는 것처럼 뭔가 이상한 일을 하고 있지 않는 한 이것은 미용적입니다.

PostCSS

PostCSS 설정 파일은 여전히 준수됩니다. postcss.config.js는 계속 작동합니다. 여기서는 변경이 필요 없습니다.

배포 및 CI 파이프라인 업데이트

우리의 배포 대상은 주로 Vercel 및 AWS (SST/OpenNext를 통해)입니다. 변경된 것은 다음과 같습니다:

Vercel: Next.js 16을 자동으로 감지하고 Turbopack을 사용했습니다. 빌드 캐시 통합은 즉시 작동합니다. Vercel이 Turbopack의 캐싱 레이어와 깊은 통합을 가지고 있기 때문에 Vercel의 빌드 시간은 로컬 CI보다 훨씬 더 극적으로 떨어졌습니다. 프로젝트 C는 Vercel에서 약 8분에서 약 2.5분으로 갔습니다.

AWS/OpenNext: Turbopack 출력 지원을 추가한 OpenNext 4.x로 업데이트해야 했습니다. 출력 형식이 약간 변경되었습니다 — .next 디렉토리 구조가 재구성되었습니다 — 따라서 특정 파일 경로를 참조한 모든 빌드 후 스크립트를 업데이트해야 했습니다.

Docker 빌드: Docker에서 Next.js를 빌드하고 있다면, 기본 이미지를 Node 20+로 업데이트하고 Turbopack의 캐시 디렉토리 (.next/cache/turbopack)가 Docker 레이어 캐싱 전략에 포함되어야 한다는 점을 주의하세요. 우리는 이것을 위해 특정 COPY 레이어를 추가했습니다.

# Turbopack을 위한 Docker 레이어 캐싱 최적화
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Turbopack을 위한 캐시 마운트
RUN --mount=type=cache,target=/app/.next/cache \
    npm run build

아직 마이그레이션하지 말아야 할 때

이것이 모두 장미라고 생각하고 싶지는 않습니다. 지금 Next.js 15에 남아 있을 합법적인 이유가 있습니다:

  • 무거운 webpack 플러그인 의존성: 빌드가 Turbopack 동등물을 가지지 않은 3개 이상의 사용자 정의 webpack 플러그인에 의존한다면, 마이그레이션 비용이 가치가 없을 수 있습니다.
  • 공유 webpack 설정이 있는 모노레포: Next.js 및 비 Next.js 프로젝트 전체에서 webpack 설정을 공유하고 있다면, 그 설정을 분할하는 것은 추가 작업입니다.
  • 안정성 요구 사항: Next.js 16.0은 안정적이지만, 16.1 및 16.2는 실제 버그를 수정했습니다. 다운타임을 견딜 수 없는 것을 마이그레이션하기 전에 최소한 16.2+를 기다릴 것입니다.
  • React 18에 갇힌 레거시 의존성: 중요한 의존성이 React 19 지원을 추가하지 않았다면, 어쨌든 차단됩니다.

헤드리스 CMS 개발을 수행하는 팀의 경우, CMS 기반 사이트는 더 간단한 빌드 구성을 가지는 경향이 있기 때문에 마이그레이션이 일반적으로 더 매끄럽습니다.

FAQ

Turbopack이 Next.js 16에서 프로덕션에 충분히 안정적입니까?

네. Turbopack은 3년 이상 개발되었고, 수백만 개의 Next.js 프로젝트 전체에서 개발 모드에서 전투 테스트되었으며, Next.js 15.3-15.5 동안 프로덕션 빌드에서 장기간 베타를 거쳤습니다. 우리는 16.0 릴리스 이후로 번들러 관련 문제 없이 여러 클라이언트 사이트의 프로덕션에서 이를 실행해왔습니다. 즉, 16.0 특히 켜져 있다면, 여러 엣지 케이스 버그가 해결된 16.2+로 업그레이드합니다.

Next.js 16에서 여전히 webpack을 사용할 수 있습니까?

아니요, 주 번들러로서는 아닙니다. Turbopack은 Next.js 16의 유일하게 지원되는 번들러입니다. webpack이 절대적으로 필요하다면, 2026년 초까지 보안 패치를 받을 Next.js 15에 남아 있어야 합니다. Vercel은 Next.js의 webpack 지원이 완료되었음을 명확하게 했습니다.

Turbopack이 webpack과 비교하여 프로덕션 빌드의 경우 얼마나 빠릅니까?

콜드 빌드 (캐시 없음)에서 우리는 20-30% 개선을 보았습니다. 웜/캐시된 빌드에서 개선은 50-70%로 점프합니다. 정확한 수치는 프로젝트 크기, 라우트 수, 정적 생성의 양에 크게 달려 있습니다. 메모리 사용량도 우리 프로젝트 전체에서 일관되게 20-30% 감소했습니다.

Turbopack을 위해 next.config.js를 다시 작성해야 합니까?

next.config.js에 사용자 정의 webpack 구성이 있으면, 네 — 이러한 블록은 turbopack 구성 형식으로 변환되어야 합니다. 표준 Next.js 설정 옵션 (이미지, 리디렉션, 재작성, 환경 변수)만 사용하고 있다면, 이 모든 것은 정확히 동일하게 작동합니다. 마이그레이션 노력은 당신이 가진 사용자 정의 webpack 설정의 양에 정확히 비례합니다.

내 기존 CI/CD 파이프라인은 Next.js 16과 함께 작동합니까?

대부분 네. 업데이트할 주요 것들은: Node.js 버전 (최소 20), webpack 관련 출력 파일을 참조하는 모든 스크립트, .next/cache/webpack을 대상으로 하는 모든 캐싱 전략입니다. 대신 .next/cache/turbopack을 캐시하고 싶을 것입니다. Vercel에 배포하고 있다면, 모든 것이 자동으로 처리됩니다.

Turbopack이 webpack과 동일한 모든 기능을 지원합니까?

Next.js 관련 기능의 경우, 네 — App Router, Pages Router, API 라우트, 미들웨어, ISR, SSG, SSR 모두 작동합니다. 사용자 정의 webpack 구성의 경우, 약 90% 기능 패리티가 있습니다. 나머지 간격은 대부분 틈새 webpack 플러그인과 매우 사용자 정의 청크 분할 전략입니다. Turbopack 호환성 문서에서 특정 사용 경우를 확인합니다.

Next.js 16으로 마이그레이션해야 합니까, 아니면 Astro 같은 대안을 고려해야 합니까?

그것은 당신의 사용 경우에 달려 있습니다. 복잡한 상태 관리로 매우 상호작용적인 애플리케이션을 구축하고 있다면, Next.js 16은 강력한 선택이고 Turbopack 개선은 DX를 크게 향상시킵니다. 최소한의 상호작용으로 콘텐츠가 풍부한 사이트를 구축하고 있다면, Astro는 그 부분 하이드레이션 모델로 뛰어난 대안으로 남아 있습니다. 우리는 둘 다와 함께 구축해왔고 프로젝트 요구 사항에 따라 선택하고 있습니다. 확실하지 않다면, 우리에게 문의하세요 우리가 평가하는 데 도움을 드릴 수 있습니다.

중간 크기의 Next.js 15 앱을 16으로 마이그레이션하는 데 필요한 최소 시간은 얼마입니까?

일반적인 중간 크기의 애플리케이션 (50-200 라우트, 표준 의존성, 최소 사용자 정의 webpack 설정)의 경우, 개발자 2-4일을 예산합니다. 여기에는 의존성 업데이트, 비동기 API 마이그레이션, 테스트, 배포 검증이 포함됩니다. 광범위한 사용자 정의 webpack 구성이나 레거시 의존성이 있으면 일주일 이상 걸릴 수 있습니다. Social Animal의 우리 팀은 인프라 작업에 스프린트를 태우고 싶지 않다면 마이그레이션 서비스를 제공합니다.