0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vercel最適化:ビルド時間短縮とレスポンス改善の実践

Last updated at Posted at 2025-12-13

この記事は、ひとりでつくるSaaS - 設計・実装・運用の記録 Advent Calendar 2025 の13日目の記事です。

昨日の記事では「Route HandlerからHonoへの移行」について書きました。この記事では、ビルド時間短縮とVercelでのレスポンス改善のために実践した最適化について解説します。

🎯 なぜVercel最適化が必要か

個人開発でNext.jsアプリをVercelにデプロイしていると、いくつかの課題が見えてきます。

  • ビルド時間の増加: 依存パッケージやページが増えるたびに、デプロイ完了までの待ち時間が伸びる
  • レスポンスの遅延: 特にコールドスタート時や、大きなバンドルを読み込むページで顕著
  • リソースの無駄遣い: 開発環境と本番環境で同じ設定を使い、キャッシュを活用できていない

この記事では、これらの課題に対して実践した最適化を紹介します。

⏱️ ビルド時間短縮の施策

Bunへの移行

ローカル開発では、パッケージマネージャをnpmからBunに移行しました。

# Before
npm install  # 数十秒〜数分

# After
bun install  # 数秒

Bunはnpmと互換性がありながら、インストール速度が大幅に高速です。依存パッケージが多いプロジェクトほど効果を実感できます。

移行は簡単で、bun installを実行するだけでbun.lockが生成されます。既存のpackage.jsonはそのまま使えます。

# 移行手順
bun install
rm package-lock.json  # 不要になったら削除

Vercelでもvercel.jsoninstallCommandbun installに変更すれば使えますが、--legacy-peer-depsが必要な依存関係があるため、互換性を考慮してnpmを使っています。ローカル開発の効率は大幅に向上しました。

パッケージインポートの最適化

next.config.tsoptimizePackageImportsで、大型ライブラリのtree-shakingを改善できます。

// next.config.ts
const nextConfig: NextConfig = {
  experimental: {
    optimizePackageImports: [
      'lucide-react',
      '@radix-ui/react-icons',
      '@radix-ui/react-dialog',
      '@radix-ui/react-dropdown-menu',
      '@tiptap/react',
      'echarts',
      'framer-motion',
      'date-fns',
      'recharts',
    ],
  },
};

これらのライブラリは、全体をインポートするとバンドルサイズが大きくなりがちです。この設定で、使用している部分だけがバンドルに含まれるようになります。

TypeScriptのインクリメンタルビルド

tsconfig.jsonでインクリメンタルビルドを有効にすると、変更がないファイルの再コンパイルをスキップできます。

{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": ".next/cache/tsconfig.tsbuildinfo",
    "skipLibCheck": true
  }
}
  • incremental: true: 増分ビルドを有効化
  • tsBuildInfoFile: ビルド情報のキャッシュ先を指定
  • skipLibCheck: node_modules内の型チェックをスキップ

🚀 レスポンス改善の施策

リージョン設定による劇的改善

リージョン設定は、最も効果を実感しやすい最適化です。有識者にとっては当たり前の設定かもしれませんが、初心者は見落としがちなポイントです。実際に開発中に体験したエピソードを紹介します。

ダッシュボード画面の読み込みが、開発環境では2秒程度なのに、本番環境では5秒以上かかっていました。

原因を調べたところ、Vercel FunctionsがデフォルトでワシントンDC(iad1)で実行されていました。データベースは東京(Supabase ap-northeast-1)にあるため、毎回太平洋を往復していたのです。

開発環境(ローカル):
ローカルPC(日本) → Supabase DB(東京) = 速い

本番環境(修正前):
Vercel Functions(ワシントンDC) → Supabase DB(東京) = 遅い

vercel.jsonにリージョン設定を追加するだけで解決しました。

{
  "regions": ["hnd1"]
}

hnd1は東京リージョンを指します。この1行を追加してデプロイしたところ、ダッシュボードの読み込みが5秒から2秒以下に改善されました。

実際にどのリージョンで実行されているかは、レスポンスヘッダーのx-vercel-idで確認できます。

修正前: hnd1::iad1::xxxxx
修正後: hnd1::hnd1::xxxxx

x-vercel-idの読み方は以下の通りです。

  • 1つ目: エッジのリージョン
  • 2つ目: Functionsのリージョン
  • 3つ目: リクエストID

エッジとFunctionsの違い

Vercelには「エッジ」と「Functions」という2種類の実行環境があります。

エッジ(Edge Network):

  • 役割: 静的ファイル配信、キャッシュ、リクエストのルーティング
  • 場所: 世界中に数百箇所(CDN)
  • 特徴: ユーザーに最も近い場所から応答

Functions(Serverless Functions):

  • 役割: APIルート、SSR、DB接続などの動的処理
  • 場所: 設定されたリージョン(今回は東京 hnd1)
  • 特徴: Node.jsランタイムで実行、DB接続が可能

リクエストの流れはこうなります。

ユーザー(日本)
    ↓
エッジ(東京)← 静的ファイルはここで返す
    ↓
Functions(東京)← API呼び出し、DB接続
    ↓
Supabase DB(東京)

DBと同じリージョンにFunctionsを配置することで、遅延を最小化できます。

キャッシュ戦略

next.config.tsheaders()で、リソースの種類ごとにキャッシュを設定します。リソースの性質に応じて適切なキャッシュ戦略を選ぶことが重要です。

静的アセット(/_next/static/):

{
  source: '/_next/static/(.*)',
  headers: [
    { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' },
  ],
}

Next.jsの静的アセットはファイル名にハッシュが含まれるため、内容が変わればURLも変わります。古いキャッシュが問題になることがないため、1年間の長期キャッシュが可能です。

HTMLページ:

{
  source: '/(.*)',
  headers: [
    { key: 'Cache-Control', value: 'public, max-age=0, must-revalidate' },
  ],
}

HTMLは動的に変わる可能性があるため、毎回サーバーに確認します。ただし、変更がなければ304レスポンスで効率的に処理されます。

API:

{
  source: '/api/(.*)',
  headers: [
    { key: 'Cache-Control', value: 'no-cache, no-store, must-revalidate' },
  ],
}

APIは認証情報やユーザー固有のデータを返すことがあるため、キャッシュを完全に無効化しています。古いデータが返されると不整合が発生するリスクがあります。

APIタイムアウトの設定

vercel.jsonで、処理時間がかかるAPIのタイムアウトを個別に設定できます。

{
  "functions": {
    "src/app/api/search/route.ts": { "maxDuration": 30 },
    "src/app/api/chat/route.ts": { "maxDuration": 60 },
    "src/app/api/embeddings/route.ts": { "maxDuration": 30 }
  }
}

Hobbyプランのデフォルトタイムアウトは10秒ですが、LLMを使った処理やベクトル検索など時間がかかるAPIは個別に延長します。

その他のレスポンス最適化

  • Edge Functions: export const runtime = 'edge'で軽量な処理をエッジで実行(OG画像生成など)
  • フォント最適化: next/fontで必要なサブセット・ウェイトのみを読み込み
  • Middlewareの最適化: matcher設定で、静的ファイルやAPIルートはMiddlewareをスキップ
  • 画像最適化: next/imageでWebP変換・リサイズを自動化

🎉 最適化の効果

これらの最適化を適用した結果をまとめます。

項目 Before After
ローカルインストール npm(数十秒) Bun(数秒)
リージョン iad1(ワシントンDC) hnd1(東京)
ダッシュボード表示 5秒以上 2秒以下
静的アセット 毎回取得 1年間キャッシュ

特にリージョン設定は、1行の変更で体感できるレベルの改善が得られました。

✅ まとめ

Vercelでのパフォーマンス最適化について解説しました。

ビルド時間短縮:

  • Bunへの移行でローカル開発を高速化
  • optimizePackageImportsで大型ライブラリを最適化
  • TypeScriptのインクリメンタルビルドを有効化

レスポンス改善:

  • DBと同じリージョンにFunctionsを配置(5秒→2秒)
  • リソースの種類ごとにキャッシュ戦略を設定
  • APIタイムアウトを処理内容に応じて調整

個人開発では、最初から完璧な最適化は不要です。ユーザーからのフィードバックやVercel Analyticsを見ながら、必要な箇所から改善していくのがおすすめです。

明日は「モバイルファーストで最適なUXを考える」について解説します。


シリーズの他の記事

  • 12/12: Next.js Route HandlerからHonoへ:API設計が楽になった理由
  • 12/14: モバイルファーストで最適なUXを考える:レスポンシブ設計の実践
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?