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

4 か月前
8

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

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

🎯 なぜVercel最適化が必要か

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

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

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

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

Bunへの移行

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

https://bun.com/

# 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変換・リサイズを自動化

🎉 最適化の効果

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

項目BeforeAfter
ローカルインストール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
Vercel最適化:ビルド時間短縮とレスポンス改善の実践
0
投稿
0
フォロワー
0
いいね

プロパティ

ページ
テクノロジー
TECH