Turbopack完全ガイド - Next.jsで実現する超高速ビルド環境


はじめに

Turbopackは、Vercel社が開発したRust製の次世代バンドラーで、Next.js 15から安定版として正式にサポートされました。Webpackと比較して最大10倍の速度を実現します。

Turbopackとは

特徴:
✅ Rust製で超高速
✅ Next.js専用に最適化
✅ Webpack代替として設計
✅ インクリメンタルビルド
✅ HMRが超高速(50-100ms)

速度比較

開発サーバー起動(3000コンポーネント):
  Webpack: 16.5秒
  Turbopack: 1.2秒 (13.8倍高速)

HMR(Hot Module Replacement):
  Webpack: 500ms
  Turbopack: 50ms (10倍高速)

コールドビルド:
  Webpack: 45秒
  Turbopack: 8秒 (5.6倍高速)

セットアップ

Next.js 15のインストール

# 新規プロジェクト作成
npx create-next-app@latest my-app --use-npm
cd my-app

# 対話形式で選択
 TypeScript: Yes
 ESLint: Yes
 Tailwind CSS: Yes
 src/ directory: Yes
 App Router: Yes
 Turbopack: Yes これを選択

既存プロジェクトでの有効化

# Next.js 15にアップグレード
npm install next@latest react@latest react-dom@latest

# package.jsonを確認
# next@15.0.0以上であることを確認
// package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start"
  }
}

プロジェクト構造

my-app/
├── src/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   └── globals.css
│   ├── components/
│   │   └── ...
│   └── lib/
│       └── ...
├── public/
├── next.config.ts
├── tsconfig.json
└── package.json

基本的な設定

next.config.ts

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  // Turbopackは--turbopackフラグで有効化
  // next.config内での設定は不要

  // 画像最適化
  images: {
    domains: ['example.com'],
  },

  // 環境変数
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },

  // リダイレクト
  async redirects() {
    return [
      {
        source: '/old-path',
        destination: '/new-path',
        permanent: true,
      },
    ];
  },
};

export default nextConfig;

TypeScript設定

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Turbopackの特徴

インクリメンタルビルド

Turbopackは変更されたファイルのみを再ビルドします。

// src/app/page.tsx
export default function Home() {
  return (
    <main>
      <h1>Hello Turbopack</h1>
      {/* ここを変更 */}
    </main>
  );
}

// 変更を保存 → 50ms以内でHMR完了

並列処理

Turbopackの内部処理:
1. ファイル変更検知(Rustのwatcher)
2. 依存関係解析(並列)
3. トランスパイル(並列)
4. バンドル(並列)
5. HMR(50ms)

Webpackの内部処理:
1. ファイル変更検知
2. 依存関係解析(シーケンシャル)
3. トランスパイル(シーケンシャル)
4. バンドル(シーケンシャル)
5. HMR(500ms)

キャッシング

# キャッシュディレクトリ
.next/cache/webpack  # Webpack
.next/cache/turbopack # Turbopack

# キャッシュクリア
rm -rf .next

CSS/Tailwind対応

Tailwind CSS設定

# インストール(create-next-appで自動)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.ts
import type { Config } from 'tailwindcss';

const config: Config = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

export default config;
/* src/app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

CSS Modules

// src/components/Button.module.css
.button {
  padding: 10px 20px;
  background-color: blue;
  color: white;
  border-radius: 4px;
}

.button:hover {
  background-color: darkblue;
}
// src/components/Button.tsx
import styles from './Button.module.css';

export function Button({ children }: { children: React.ReactNode }) {
  return <button className={styles.button}>{children}</button>;
}

SCSS/Sass

npm install -D sass
// src/styles/variables.scss
$primary-color: #0070f3;
$secondary-color: #ff4081;

@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}
// src/app/page.tsx
import '@/styles/variables.scss';

アセット処理

画像最適化

// src/app/page.tsx
import Image from 'next/image';

export default function Home() {
  return (
    <div>
      {/* 静的インポート */}
      <Image
        src="/logo.png"
        alt="Logo"
        width={200}
        height={100}
        priority // LCP最適化
      />

      {/* 外部画像 */}
      <Image
        src="https://example.com/image.jpg"
        alt="External"
        width={600}
        height={400}
        loading="lazy"
      />
    </div>
  );
}

フォント最適化

// src/app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
});

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

SVG処理

// SVGをコンポーネントとして使用
import Logo from '@/public/logo.svg';

export default function Header() {
  return (
    <header>
      <Logo width={100} height={50} />
    </header>
  );
}

環境変数

設定

# .env.local
DATABASE_URL=postgresql://localhost:5432/mydb
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_KEY=secret123
// src/app/page.tsx
export default function Home() {
  // クライアント側で使用可能(NEXT_PUBLIC_プレフィックス必須)
  const apiUrl = process.env.NEXT_PUBLIC_API_URL;

  return <div>API URL: {apiUrl}</div>;
}
// src/app/api/data/route.ts
export async function GET() {
  // サーバー側でのみ使用可能
  const dbUrl = process.env.DATABASE_URL;
  const secretKey = process.env.SECRET_KEY;

  // ...
}

環境別設定

# .env.development
NEXT_PUBLIC_API_URL=http://localhost:3000/api

# .env.production
NEXT_PUBLIC_API_URL=https://api.production.com

# .env.test
NEXT_PUBLIC_API_URL=http://localhost:3001/api

パフォーマンス最適化

Code Splitting

// 動的インポート
import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(() => import('@/components/Heavy'), {
  loading: () => <p>Loading...</p>,
  ssr: false, // クライアント側のみ
});

export default function Page() {
  return (
    <div>
      <DynamicComponent />
    </div>
  );
}

Route Groups

src/app/
├── (marketing)/
│   ├── layout.tsx
│   ├── page.tsx
│   └── about/
│       └── page.tsx
└── (shop)/
    ├── layout.tsx
    ├── products/
    │   └── page.tsx
    └── cart/
        └── page.tsx
// src/app/(marketing)/layout.tsx
export default function MarketingLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <header>Marketing Header</header>
      {children}
    </div>
  );
}

Parallel Routes

src/app/
├── @analytics/
│   └── page.tsx
├── @team/
│   └── page.tsx
├── layout.tsx
└── page.tsx
// src/app/layout.tsx
export default function Layout({
  children,
  analytics,
  team,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
}) {
  return (
    <div>
      {children}
      {analytics}
      {team}
    </div>
  );
}

Server Actions

// src/app/actions.ts
'use server';

export async function createTodo(formData: FormData) {
  const title = formData.get('title') as string;

  // データベース操作
  // await db.todo.create({ data: { title } });

  return { success: true };
}
// src/app/page.tsx
import { createTodo } from './actions';

export default function Page() {
  return (
    <form action={createTodo}>
      <input name="title" type="text" required />
      <button type="submit">Add Todo</button>
    </form>
  );
}

WebpackからTurbopackへの移行

互換性チェック

✅ 完全互換:
- TypeScript
- CSS/SCSS/Sass
- CSS Modules
- Tailwind CSS
- PostCSS
- 画像最適化
- フォント最適化

⚠️ 部分互換:
- カスタムwebpack設定
- 一部のwebpackプラグイン

❌ 非互換:
- next.config.jsのwebpackプロパティ

カスタムwebpack設定の移行

// next.config.js(Webpack)
module.exports = {
  webpack: (config, { isServer }) => {
    // カスタム設定
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    });
    return config;
  },
};
// Turbopackでは現在未対応
// 代替案: 標準のSVGインポートを使用
import Logo from '@/public/logo.svg';

段階的移行

// package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "dev:webpack": "next dev",
    "build": "next build"
  }
}
# Turbopackで開発
npm run dev

# Webpackで開発(問題があった場合)
npm run dev:webpack

トラブルシューティング

エラー1: モジュールが見つからない

エラー: Module not found: Can't resolve '@/components/Button'

解決策:
// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

エラー2: HMRが動作しない

原因:
- ファイルウォッチャーの制限
- ファイルシステムの問題

解決策:
# macOS
brew install watchman

# Linux
sudo sysctl fs.inotify.max_user_watches=524288

エラー3: ビルドが遅い

改善策:

1. キャッシュクリア
rm -rf .next

2. node_modulesクリア
rm -rf node_modules
npm install

3. Turbopackバージョン確認
npm list next

エラー4: CSS Modulesのクラス名が壊れる

// ❌ 動的クラス名は避ける
<div className={styles[`button-${variant}`]} />

// ✅ 静的クラス名を使用
<div className={variant === 'primary' ? styles.buttonPrimary : styles.buttonSecondary} />

ベンチマーク

実測例1: 小規模アプリ

プロジェクト: 50コンポーネント、5,000行

Webpack:
  開発サーバー起動: 8.2秒
  HMR: 300ms

Turbopack:
  開発サーバー起動: 1.1秒 (7.5倍高速)
  HMR: 40ms (7.5倍高速)

実測例2: 中規模アプリ

プロジェクト: 500コンポーネント、50,000行

Webpack:
  開発サーバー起動: 25秒
  HMR: 800ms

Turbopack:
  開発サーバー起動: 3.2秒 (7.8倍高速)
  HMR: 80ms (10倍高速)

実測例3: 大規模アプリ

プロジェクト: 3,000コンポーネント、300,000行

Webpack:
  開発サーバー起動: 120秒
  HMR: 2,000ms

Turbopack:
  開発サーバー起動: 15秒 (8倍高速)
  HMR: 150ms (13.3倍高速)

実践的なユースケース

Eコマースサイト

// src/app/products/[id]/page.tsx
import Image from 'next/image';

export default async function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  // データ取得
  const product = await getProduct(params.id);

  return (
    <div>
      <Image
        src={product.image}
        alt={product.name}
        width={600}
        height={600}
        priority
      />
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <button>Add to Cart</button>
    </div>
  );
}

ブログ

// src/app/blog/[slug]/page.tsx
import { MDXRemote } from 'next-mdx-remote/rsc';

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <MDXRemote source={post.content} />
    </article>
  );
}

ダッシュボード

// src/app/dashboard/page.tsx
import { Suspense } from 'react';

async function Analytics() {
  const data = await getAnalytics();
  return <div>{/* グラフ表示 */}</div>;
}

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading analytics...</div>}>
        <Analytics />
      </Suspense>
    </div>
  );
}

まとめ

Turbopackの強み

  1. 圧倒的速度: Webpackの10倍
  2. Next.js最適化: App Routerと完全統合
  3. 開発体験: 瞬時のHMR
  4. 安定性: 本番環境対応

ベストプラクティス

  • 開発環境でTurbopack使用
  • 本番ビルドは標準(Webpack)
  • Code Splittingで最適化
  • 画像・フォント最適化を活用

いつTurbopackを使うべきか

  • Next.js 15以上
  • 大規模プロジェクト(500+コンポーネント)
  • 開発速度を最優先
  • Webpackカスタム設定が少ない

次のステップ

Turbopackで、開発体験を劇的に向上させましょう。