junyeokk
Blog
Next.js·2025. 08. 08

Turbopack

Next.js 프로젝트의 규모가 커지면 개발 서버의 시작 시간과 HMR(Hot Module Replacement) 속도가 체감될 정도로 느려진다. 파일을 하나 수정하고 브라우저에 반영될 때까지 2~3초씩 걸리면, 하루에 수백 번 저장하는 개발자 입장에서 생산성이 크게 떨어진다.

Webpack은 JavaScript로 작성된 번들러다. 싱글 스레드 기반이고, 파일이 변경되면 관련된 모듈 그래프를 다시 순회하면서 번들을 재생성한다. 프로젝트가 작을 때는 문제가 없지만, 수천 개의 모듈을 가진 프로젝트에서는 이 과정이 병목이 된다.

Turbopack은 이 문제를 해결하기 위해 Rust로 작성된 번들러다. Next.js에 내장되어 있고, 개발 환경에서 Webpack을 대체한다.


Webpack과 뭐가 다른가

Rust 기반 병렬 처리

Webpack은 JavaScript 싱글 스레드에서 동작한다. 아무리 CPU 코어가 많아도 한 번에 하나의 작업만 처리할 수 있다. Turbopack은 Rust로 작성되어 있어서 여러 코어에 작업을 분산할 수 있다. 모듈 파싱, 트랜스폼, 번들링을 동시에 처리하기 때문에 코어 수가 많을수록 빨라진다.

함수 레벨 캐싱

Webpack도 캐시를 사용하지만, 모듈 단위로 캐싱한다. 모듈 하나가 변경되면 해당 모듈 전체를 다시 처리해야 한다.

Turbopack은 이보다 더 세밀한 함수 레벨 캐싱을 사용한다. 내부적으로 작업을 작은 함수 단위로 나누고, 각 함수의 입력과 출력을 캐싱한다. 파일이 변경되면 실제로 영향받는 함수만 다시 실행하고, 나머지는 캐시된 결과를 재사용한다. 이게 "incremental computation"이라고 불리는 핵심 아키텍처다.

text
// Webpack: 모듈 A 변경 → 모듈 A 전체 재처리
// Turbopack: 모듈 A 변경 → 변경된 함수만 재실행, 나머지 캐시 hit

Lazy Bundling

Webpack은 개발 서버를 시작할 때 전체 애플리케이션을 번들링한다. 100개의 페이지가 있는 프로젝트에서 localhost:3000/about 하나만 보려고 해도 나머지 99개 페이지까지 다 번들링한다.

Turbopack은 실제로 요청된 페이지만 번들링한다. 개발 서버를 시작해도 아무것도 번들링하지 않고, 브라우저가 특정 경로를 요청하면 그때 필요한 모듈만 처리한다. 그래서 프로젝트 규모와 상관없이 초기 시작이 빠르다.

네이티브 ESM과의 비교

Vite 같은 도구는 개발 환경에서 번들링을 하지 않고, 브라우저의 네이티브 ESM(ES Modules)을 활용한다. 각 모듈을 개별 HTTP 요청으로 로드하는 방식이다.

text
// 네이티브 ESM 방식 (Vite)
// 브라우저가 각 모듈을 개별 요청
GET /src/App.tsx
GET /src/components/Header.tsx
GET /src/components/Sidebar.tsx
GET /src/utils/format.ts
// ... 수백 개의 요청

소규모 프로젝트에서는 이 방식이 빠르다. 하지만 모듈이 수백, 수천 개가 되면 브라우저가 처리해야 할 HTTP 요청이 폭발적으로 늘어나면서 오히려 느려진다. 특히 모듈 간 의존 관계가 깊으면 waterfall 현상이 발생한다 — A를 로드해야 B를 요청할 수 있고, B를 로드해야 C를 요청할 수 있는 식이다.

Turbopack은 개발 환경에서도 번들링을 하되, lazy bundling과 incremental computation으로 불필요한 작업을 최소화한다. 네이티브 ESM의 "요청 폭발" 문제 없이, 번들러의 최적화 이점은 그대로 유지하는 접근이다.

통합 그래프

Next.js는 서버 컴포넌트와 클라이언트 컴포넌트를 동시에 다뤄야 한다. Webpack 기반에서는 이를 위해 여러 개의 컴파일러를 돌리고 결과를 합쳐야 했다.

Turbopack은 서버와 클라이언트를 하나의 통합 그래프(unified graph)로 관리한다. 하나의 모듈 그래프 안에서 "이 모듈은 서버용", "이 모듈은 클라이언트용"을 구분하고, 각각에 맞는 번들을 생성한다. 컴파일러를 여러 개 돌리는 오버헤드가 없다.


사용 방법

Next.js 15부터 Turbopack이 안정 버전으로 포함되어 있다. next dev 명령에 --turbopack 플래그를 추가하면 된다.

json
{
  "scripts": {
    "dev": "next dev --turbopack"
  }
}

Next.js 16부터는 Turbopack이 기본 번들러가 되어 별도 플래그 없이 사용된다. Webpack을 사용하고 싶으면 오히려 --webpack 플래그를 명시해야 한다.

bash
# Next.js 15: Turbopack 활성화
next dev --turbopack

# Next.js 16+: 기본이 Turbopack, Webpack으로 전환하려면
next dev --webpack

별도의 설정 파일이나 설치가 필요 없다. Next.js에 내장되어 있기 때문에 플래그 하나만 바꾸면 바로 전환할 수 있다.


내부 동작: SWC

Turbopack은 내부적으로 SWC(Speedy Web Compiler)를 사용해서 JavaScript와 TypeScript를 트랜스파일한다. SWC 역시 Rust로 작성된 컴파일러로, Babel보다 수십 배 빠르다.

text
소스 코드 (.tsx)

SWC (파싱 + 트랜스폼)    ← Rust, 병렬 처리

Turbopack (번들링)        ← Rust, incremental

브라우저에 전달

SWC가 JSX, TypeScript, 최신 ECMAScript 문법을 변환하고, Turbopack이 모듈을 묶어서 브라우저에 전달하는 구조다. 단, SWC는 타입 체크를 하지 않는다. 타입 체크는 별도로 tsc --watch를 실행하거나 IDE에 맡겨야 한다.


CSS 처리: Lightning CSS

Turbopack은 CSS 처리에 Lightning CSS를 사용한다. PostCSS와 달리 Rust로 작성되어 있어서 CSS 파싱, 트랜스폼, 압축이 매우 빠르다.

지원하는 기능:

  • 글로벌 CSS: .css 파일 직접 import
  • CSS Modules: .module.css 파일 네이티브 지원
  • CSS Nesting: 모던 CSS 중첩 문법
  • PostCSS: postcss.config.js가 있으면 자동 처리 (Tailwind CSS 포함)
  • Sass/SCSS: Next.js에서 기본 지원
css
/* CSS Nesting - Turbopack이 자동 변환 */
.card {
  background: white;
  
  &:hover {
    background: #f0f0f0;
  }
  
  & .title {
    font-size: 1.5rem;
  }
}

next.config.js 설정

Turbopack 관련 설정은 next.config.js(또는 .ts)의 turbopack 키 아래에 작성한다.

resolveAlias

Webpack의 resolve.alias에 해당한다. 특정 모듈의 import 경로를 다른 경로로 매핑할 수 있다.

ts
// next.config.ts
const nextConfig = {
  turbopack: {
    resolveAlias: {
      // Webpack의 resolve.alias와 동일한 역할
      '@components': './src/components',
      'underscore': 'lodash',
    },
  },
};

resolveExtensions

모듈을 import할 때 확장자를 생략하면 Turbopack이 시도하는 확장자 목록이다.

ts
const nextConfig = {
  turbopack: {
    resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
  },
};

rules (Webpack 로더 호환)

일부 Webpack 로더를 Turbopack에서도 사용할 수 있다. rules 설정으로 특정 파일 확장자에 로더를 매핑한다.

ts
const nextConfig = {
  turbopack: {
    rules: {
      '*.svg': {
        loaders: ['@svgr/webpack'],
        as: '*.js',
      },
    },
  },
};

모든 Webpack 로더가 호환되는 것은 아니다. Turbopack의 아키텍처와 맞지 않는 로더(예: Sass의 커스텀 JavaScript 함수)는 동작하지 않을 수 있다.


Webpack에서 전환할 때 주의할 점

Webpack 전용 설정은 무시된다

next.config.jswebpack 콜백은 Turbopack에서 실행되지 않는다. Webpack 플러그인이나 로더를 커스텀으로 설정했다면, Turbopack의 대응 설정으로 마이그레이션해야 한다.

ts
// ❌ Turbopack에서 무시됨
const nextConfig = {
  webpack: (config) => {
    config.module.rules.push({ ... });
    return config;
  },
};

// ✅ Turbopack용 설정
const nextConfig = {
  turbopack: {
    rules: { ... },
  },
};

프로덕션 빌드는 아직 Webpack

Turbopack은 현재 개발 환경(next dev)에서만 사용된다. next build로 프로덕션 빌드를 할 때는 여전히 Webpack이 사용된다. 즉, 개발과 프로덕션에서 번들러가 다르다는 점을 인지해야 한다. 대부분의 경우 문제가 없지만, 번들러 특유의 동작에 의존하는 코드가 있다면 주의가 필요하다.

지원되지 않는 기능

몇 가지 Webpack 기능은 Turbopack에서 아직 지원되지 않는다:

  • webpack() 콜백: 위에서 언급한 대로 완전히 무시됨
  • Sass 커스텀 함수: sassOptions.functions는 동작하지 않음 (Rust 환경에서 JavaScript 함수 실행 불가)
  • 일부 CSS Modules 기능: :local/:global을 독립 pseudo-class로 사용하는 것은 미지원
  • AMD 모듈: 기본적인 것은 동작하지만, 복잡한 AMD 패턴은 제한적

실제 성능 차이

공식 벤치마크에 따르면 Turbopack은 대규모 프로젝트에서 큰 차이를 보인다:

항목WebpackTurbopack
콜드 스타트느림 (전체 번들링)빠름 (lazy bundling)
HMR모듈 단위 재빌드함수 단위 캐시
메모리 사용높음상대적으로 낮음

다만 모든 상황에서 Turbopack이 빠른 것은 아니다. 소규모 프로젝트에서는 차이가 미미하고, 특정 Webpack 플러그인 조합에서는 오히려 Webpack이 나을 수도 있다. Turbopack의 진가는 모듈 수가 수천 개 이상인 대규모 프로젝트에서 발휘된다.


정리

Turbopack은 "더 빠른 Webpack"이 아니라, 번들러 아키텍처 자체를 다시 설계한 결과물이다. Rust 기반 병렬 처리, 함수 레벨 incremental 캐싱, lazy bundling이라는 세 가지 핵심 전략으로 대규모 프로젝트의 개발 경험을 개선한다. Next.js에 내장되어 있어서 별도 설치나 복잡한 설정 없이 플래그 하나로 전환할 수 있다는 점도 실용적이다.