32blogby Studio Mitsu

Vercelデプロイエラー完全解決ガイド

VercelへのNext.jsデプロイでよく踏むエラーパターンと解決策を体験ベースで解説。ビルド失敗からFunction Timeout、メモリ不足まで網羅。

by omitsu18 min read
Next.jsVerceldeploymenterror-fixingCI/CDtroubleshooting
目次

Vercelデプロイエラーの大半は6つに分類できる。環境変数の欠落、TypeScript/ESLintのビルドエラー、Functionタイムアウト、メモリ上限、本番限定404、キャッシュ不整合だ。この記事では全パターンの具体的な解決策を解説する。

32blog.comをVercelに本番デプロイするとき、ローカルでは npm run build が通るのにVercelに上げた瞬間にビルドが死ぬ——そういう経験を何度もしてきた。「ローカルでは動く」は「Vercelでも動く」を意味しない。環境変数、Node.jsバージョン、キャッシュの扱い、Function制限……ローカルとVercelの差異は思ったより大きい。

7つのエラーパターンと解決策を網羅しているので、エラーメッセージが出たらCtrl+Fで検索してほしい。

ビルドがローカルでは通るのにVercelで失敗する

最も頻繁に遭遇するパターンだ。原因のほとんどは「環境変数の未設定」か「依存関係の解決失敗」のどちらかに絞られる。

環境変数が設定されていない

ローカルでは .env.local に書いた値が自動で読まれる。Vercelでは環境変数をダッシュボードから明示的に設定しないと読まれない。

ビルド中に環境変数が必要なコードが実行されると、こんなエラーになる。

Error: Missing required environment variable: DATABASE_URL
Build failed with errors

または、環境変数が undefined のまま処理が進んでクラッシュするパターンもある。

TypeError: Cannot read properties of undefined (reading 'split')

解決策: Vercelダッシュボード → Project → Settings → Environment Variables に追加する。

bash
# ローカルの .env.local を確認
cat .env.local

# 以下のような変数がある場合はすべてVercelに追加が必要
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
AUTH_SECRET=...

環境変数を設定した後は「Redeploy」ではなく、新しいコミットをプッシュするか「Redeploy without cache」を選ぶと確実にビルドが通る。

TypeScriptの型エラー

ローカルでは npm run dev--turbopack で起動していて型チェックをスキップしているが、Vercelのビルドは next build を実行するため型エラーで止まる。

Type error: Property 'name' does not exist on type 'User | null'

解決策: デプロイ前にローカルで必ず型チェックを走らせる習慣をつける。

bash
# ビルド前に必ず実行する
npx tsc --noEmit

# 型エラーを修正してからプッシュ

next.config.ts で型チェックを無効化する方法もあるが、本番コードの品質が下がるので非推奨だ。

typescript
// next.config.ts(非推奨: 型チェックを無効化する設定)
const nextConfig = {
  typescript: {
    ignoreBuildErrors: true, // やむを得ない場合のみ
  },
};

ESLintエラー

next build はESLintも実行する。npm run dev では引っかからなかったESLintエラーがビルド時に出ることがある。

./app/components/Button.tsx
ESLint: 'onClick' is defined but never used. (no-unused-vars)
Failed to compile.

解決策: ローカルで事前にLintを通す。

bash
# ビルド前にLintを確認
npm run lint

# 自動修正できるものは修正
npm run lint -- --fix

急ぎの場合は next.config.ts でLintを一時的に無効化できるが、技術的負債になるので必ず後で修正する。

typescript
// next.config.ts
const nextConfig = {
  eslint: {
    ignoreDuringBuilds: true, // 一時対応のみ
  },
};

Function Timeoutエラーが出る

Vercel Functionsにはタイムアウト上限がある。Fluid Compute(2025年4月以降のデフォルト)で制限値が大幅に緩和された。

プランデフォルト最大
Hobby300秒(5分)300秒(5分)
Pro300秒(5分)800秒(13分)
Enterprise300秒(5分)800秒(13分)
Error: Task timed out after 300.00 seconds

原因と確認方法

VercelダッシュボードのFunctionsタブを開くと、各関数の実行時間が確認できる。300秒あっても上限に迫っているものがあれば要対応だ。

よくある原因は以下の通り。

  • 外部APIへのリクエストが遅い
  • データベースのクエリが最適化されていない
  • 大きなファイルをメモリ上で処理している
  • 無限ループや意図しない再帰

解決策1: maxDurationを設定する

typescript
// app/api/heavy-task/route.ts
export const maxDuration = 60; // Hobby最大300秒、Pro最大800秒

export async function GET() {
  // 重い処理
  const result = await heavyDatabaseQuery();
  return Response.json(result);
}

解決策2: 処理を非同期キューに逃がす

重い処理はAPIルートで同期的に実行せず、バックグラウンドジョブに委ねる。Vercelなら Vercel Cron Jobs や外部サービス(Upstash QStash など)を使う方法がある。

typescript
// app/api/trigger-job/route.ts
import { Client } from "@upstash/qstash";

const qstash = new Client({ token: process.env.QSTASH_TOKEN! });

export async function POST(request: Request) {
  const body = await request.json();

  // 重い処理を非同期キューに投げる
  await qstash.publishJSON({
    url: `${process.env.VERCEL_URL}/api/process-job`,
    body: body,
  });

  // すぐにレスポンスを返す
  return Response.json({ status: "queued" });
}

解決策3: Node.jsランタイムを使う(Edgeはレガシー)

Vercelは現在、ほとんどのケースでNode.jsランタイムを推奨している。Fluid Computeにより、Node.js Functionsのコールドスタートが高速化し、メモリ上限(最大4 GB)やタイムアウト(最大800秒)もEdgeより大きい。Edge Runtimeは初回レスポンス25秒制限・コードサイズ1〜2 MB制限があり、レガシー扱いだ。

typescript
// app/api/fast-api/route.ts
// Node.jsランタイムがデフォルト — 設定不要
// Edge位置での実行が必要なときだけEdgeを使う

export async function GET() {
  return Response.json({ status: "ok" });
}

メモリ不足(OOM)エラー

ビルド中または実行中にメモリが足りなくなると、このエラーが出る。

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

または Vercel のログに以下のように記録される。

Error: Function exceeded memory limit

Fluid Computeでのメモリ上限: Hobbyは2 GB / 1 vCPU、Pro/Enterpriseは最大4 GB / 2 vCPU。

ビルド時のメモリ不足

大規模なプロジェクトや、ビルド時に多くのページを静的生成する場合に起きやすい。

bash
# Node.jsのメモリ制限を増やしてビルド
NODE_OPTIONS="--max-old-space-size=4096" npm run build

Vercelのビルド設定でも同様に環境変数を追加できる。

# Vercelダッシュボード > Environment Variables
NODE_OPTIONS = --max-old-space-size=4096

実行時のメモリ不足

APIルートで大きなデータをメモリ上に展開している場合に発生する。

typescript
// ❌ 悪い例: 大きなJSONを一度にメモリに展開
export async function GET() {
  const hugeData = await fetchAllRecords(); // 100MB のデータ
  return Response.json(hugeData);
}
typescript
// ✅ 良い例: ストリーミングレスポンスを使う
export async function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      const records = await fetchRecordsInBatches();
      for await (const batch of records) {
        controller.enqueue(
          new TextEncoder().encode(JSON.stringify(batch) + "\n")
        );
      }
      controller.close();
    },
  });

  return new Response(stream, {
    headers: { "Content-Type": "application/json" },
  });
}

404エラーが本番環境だけで出る

ローカルでは正常なのに、Vercelにデプロイすると特定のページが404になるパターンがある。

動的ルートの静的生成漏れ(generateStaticParams

typescript
// app/blog/[slug]/page.tsx
// generateStaticParamsが不完全だと本番でのみ404になる

export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

generateStaticParams が返すスラッグ以外のパスにアクセスすると404になる。dynamicParamstrue(デフォルト)にすると、未生成のパスにもアクセスできるようになる。

typescript
// app/blog/[slug]/page.tsx
// dynamicParams のデフォルトは true なので、未生成パスもオンデマンドで生成される
export const dynamicParams = true;

// dynamicParams = false にすると生成されていないパスは明示的に404になる

next-intl のルーティング設定ミス

多言語対応している場合、next-intlのロケール設定が本番環境と開発環境でズレることがある。セットアップの詳細はnext-intl多言語対応ガイドを参照。

typescript
// middleware.ts
import createMiddleware from "next-intl/middleware";

export default createMiddleware({
  locales: ["ja", "en", "es"],
  defaultLocale: "ja",
  // localePrefix: "always" の場合、/ja/... が必須になる
  localePrefix: "as-needed", // デフォルトロケールはプレフィックスなし
});

export const config = {
  matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};

キャッシュが古い状態で固まる

Vercelのキャッシュ戦略が予期しない動作を引き起こすことがある。特に fetch のキャッシュとISR(Incremental Static Regeneration)の組み合わせで起きやすい。App Routerのキャッシュの仕組みについてはSSRガイドも参考にしてほしい。

typescript
// ❌ キャッシュ戦略を明示していない
export async function getArticles() {
  const res = await fetch("https://api.example.com/articles");
  // Next.js 15+ではデフォルトno-store。明示しないと意図が不明確
  return res.json();
}
typescript
// ✅ 適切なキャッシュ戦略を明示する
export async function getArticles() {
  const res = await fetch("https://api.example.com/articles", {
    // 60秒ごとに再検証
    next: { revalidate: 60 },
  });
  return res.json();
}

// または完全にキャッシュを無効化
export async function getLatestPrice() {
  const res = await fetch("https://api.example.com/price", {
    cache: "no-store",
  });
  return res.json();
}

On-Demand Revalidationを使う

コンテンツ更新があった際に即座にキャッシュを破棄したい場合は、Revalidation APIを使う。

typescript
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from "next/cache";
import { NextRequest } from "next/server";

export async function POST(request: NextRequest) {
  const token = request.nextUrl.searchParams.get("token");

  // 不正なリクエストを拒否
  if (token !== process.env.REVALIDATION_TOKEN) {
    return Response.json({ error: "Invalid token" }, { status: 401 });
  }

  const body = await request.json();

  if (body.path) {
    revalidatePath(body.path);
  }

  if (body.tag) {
    revalidateTag(body.tag);
  }

  return Response.json({ revalidated: true });
}

デプロイは成功するがCORSエラーが出る

フロントエンドからAPIを叩いたとき、本番環境だけCORSエラーが出るケースがある。

Access to fetch at 'https://api.example.com' from origin 'https://32blog.com'
has been blocked by CORS policy

Next.jsのAPIルートにCORSヘッダーを追加することで解決できる。

typescript
// app/api/data/route.ts
export async function GET(request: Request) {
  const response = await fetchData();

  return Response.json(response, {
    headers: {
      "Access-Control-Allow-Origin": "https://32blog.com",
      "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
}

// OPTIONSリクエスト(preflight)にも応答する
export async function OPTIONS() {
  return new Response(null, {
    headers: {
      "Access-Control-Allow-Origin": "https://32blog.com",
      "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
}

または next.config.ts でまとめて設定する。

typescript
// next.config.ts
const nextConfig = {
  async headers() {
    return [
      {
        source: "/api/:path*",
        headers: [
          { key: "Access-Control-Allow-Origin", value: "https://32blog.com" },
          { key: "Access-Control-Allow-Methods", value: "GET,POST,OPTIONS" },
          { key: "Access-Control-Allow-Headers", value: "Content-Type" },
        ],
      },
    ];
  },
};

export default nextConfig;

Access-Control-Allow-Origin: * はすべてのオリジンからのアクセスを許可する。パブリックなAPIなら問題ないが、認証が必要なAPIでは特定のオリジンのみ許可するよう制限すること。

FAQ

ローカルでビルド通るのにVercelで失敗するのはなぜ?

最も多い原因は環境変数の欠落だ。.env.local はローカル開発で自動的に読まれるが、Vercelではダッシュボードから明示的に設定する必要がある。次に多いのはTypeScript型エラー — Turbopackを使う npm run dev は型チェックをスキップするが、Vercelの next build はしない。push前に npx tsc --noEmit を走らせよう。

VercelのFunctionタイムアウト上限は今いくつ?

Fluid Compute(2025年4月以降のデフォルト)で全プランのデフォルトが300秒になった。Pro/Enterpriseは maxDuration最大800秒まで延長可能。Fluid Computeに移行していないレガシープロジェクトは旧制限(Hobby: 10秒、Pro: 300秒)が適用される。

Edge RuntimeとNode.jsランタイム、どっちを使うべき?

Vercelは現在Node.jsを推奨している。Fluid ComputeによりNode.js Functionsのコールドスタートが高速化し、メモリ上限(最大4 GB)もタイムアウト(最大800秒)もEdgeより大きい。Edge Runtimeは初回レスポンス25秒制限・コードサイズ1〜2 MB制限があり、レガシー扱いだ。Edge位置での実行が必要なときだけ使おう。

「Function exceeded memory limit」エラーの対処法は?

Fluid Computeでのデフォルトメモリは2 GB(Pro/Enterpriseは最大4 GB)。ビルド時のOOMなら NODE_OPTIONS=--max-old-space-size=4096 を環境変数に設定する。実行時のOOMなら、大きなデータを一括でメモリに展開するのをやめてストリーミングレスポンスに切り替える。

本番環境だけ404が出るのはなぜ?

generateStaticParamsが不完全か、ミドルウェアの設定ミスが原因なことが多い。dynamicParamsfalse の場合、generateStaticParams が返さないパスは404になる。next-intlのmiddleware matcher_nextapi、静的ファイルを正しく除外しているかも確認しよう。

Vercelのキャッシュを強制クリアするには?

VercelダッシュボードからRedeploy without cacheを選ぶか、Route Handlerでオンデマンド再検証revalidatePath() / revalidateTag())をトリガーする。ISRページなら revalidate の間隔を明示的に設定すればキャッシュが自動更新される。

Vercel Fluid Computeで何が変わった?

Fluid Compute(2025年4月以降のデフォルト)で従来のAWS Lambdaベースから移行した。主な変更: タイムアウトのデフォルトが10〜15秒→300秒、メモリが1 GB→2 GB(Pro最大4 GB)、課金がActive CPU時間ベース(I/O待ちのアイドル時間はカウントされない)、Edge Runtimeがレガシー扱いに。

まとめ

Vercelデプロイで踏むエラーのパターンをまとめる。

エラー原因解決策
ビルド失敗環境変数未設定、型エラー、ESLintエラーVercelに環境変数を追加。tsc --noEmit で事前確認
Function Timeout処理が遅い、無限ループmaxDuration(Pro最大800秒)、非同期キュー
OOMエラーメモリ不足(デフォルト2 GB)NODE_OPTIONS=--max-old-space-size=4096、ストリーミング
404(本番のみ)動的ルートの生成漏れ、ミドルウェアのmatcher設定ミスdynamicParams、matcherを見直す
キャッシュが古いfetch のキャッシュ設定不備revalidate または cache: "no-store" を明示
CORSエラーCORSヘッダー未設定APIルートまたは next.config.ts でヘッダーを追加

「ローカルで動く = Vercelで動く」ではない。デプロイ前に tsc --noEmitnpm run lint を走らせるのを習慣にするだけで、ビルド起因のエラーの半分は事前に潰せる。残りは環境変数の確認と、VercelのFunctionsログを丁寧に読むことで解決できる。

関連記事: