エラーの概要

Vercelの504エラーは、デプロイされたサーバーレス関数の実行がタイムアウトに達したことを示します。Hobbyプランでは10秒、Proプラン以上では300秒が実行時間の上限となり、この制限を超えると504 Gateway Timeoutレスポンスが返されます。一般的に、API呼び出しやデータベースクエリの応答待ちが長引く場合に発生しやすいエラーです。

実際のエラーメッセージ例

Vercelのブラウザ表示:

504
Gateway Timeout

関数ログ(Vercel Dashboard):

{
  "errorCode": "FUNCTION_INVOCATION_TIMEOUT",
  "message": "The function exceeded the timeout duration",
  "functionName": "api/users",
  "executionDurationMs": 10000,
  "region": "sfo1"
}

cURLレスポンス:

HTTP/1.1 504 Gateway Timeout
Server: Vercel
Content-Type: text/html
Connection: close

504 - Gateway Timeout

よくある原因と解決手順

原因1:Hobbyプランのタイムアウト制限(10秒)に引っかかっている

Vercelの無料Hobbyプランでは、サーバーレス関数の最大実行時間が10秒に制限されています。この時間内に処理が完了しないと自動的に504エラーが返されます。特に外部APIの呼び出しやデータベースアクセスが複数含まれる関数では、容易にこの上限に達する可能性があります。

Before(エラーが起きるコード):

// api/getUserData.js
export default async function handler(req, res) {
  // 外部APIを3つ順序実行(各3秒)→ 合計9秒
  const user = await fetch('https://api.example.com/user/123').then(r => r.json());
  const posts = await fetch('https://api.example.com/posts/123').then(r => r.json());
  const comments = await fetch('https://api.example.com/comments/123').then(r => r.json());
  
  res.status(200).json({ user, posts, comments });
}

After(修正後):

// api/getUserData.js
export default async function handler(req, res) {
  // 複数APIを並列実行(合計3秒に短縮)
  const [user, posts, comments] = await Promise.all([
    fetch('https://api.example.com/user/123').then(r => r.json()),
    fetch('https://api.example.com/posts/123').then(r => r.json()),
    fetch('https://api.example.com/comments/123').then(r => r.json())
  ]);
  
  res.status(200).json({ user, posts, comments });
}

または、Proプラン(300秒上限)へのアップグレードも検討してください。Vercel Dashboardの「Settings」→「Plan」から変更可能です。

原因2:外部API・データベース接続にタイムアウトが設定されていない

外部サービスへのリクエストがハング状態に陥ると、関数全体が待機し続けて504に陥ります。特にネットワークが不安定な環境では、接続先が応答しなくなるケースが頻繁に発生します。

Before(エラーが起きるコード):

// api/fetchUserProfile.js
import fetch from 'node-fetch';

export default async function handler(req, res) {
  // タイムアウト指定がない → 無限待ち可能性
  const response = await fetch('https://slow-api.example.com/profile');
  const data = await response.json();
  
  res.status(200).json(data);
}

After(修正後):

// api/fetchUserProfile.js
import fetch from 'node-fetch';

export default async function handler(req, res) {
  // 5秒のタイムアウトを設定
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 5000);
  
  try {
    const response = await fetch('https://slow-api.example.com/profile', {
      signal: controller.signal
    });
    clearTimeout(timeoutId);
    const data = await response.json();
    res.status(200).json(data);
  } catch (error) {
    if (error.name === 'AbortError') {
      res.status(408).json({ error: 'API request timeout' });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
}

原因3:重い同期的なデータ処理を関数内で実行している

画像リサイズ、CSV解析、機械学習推論など、CPU集約的な処理を直接関数内で行うと、瞬く間にタイムアウトに達します。これらの処理は関数の責務外に切り出し、キューイングシステムで非同期実行するべきです。

Before(エラーが起きるコード):

// api/processImage.js
import sharp from 'sharp';
import fs from 'fs';

export default async function handler(req, res) {
  // 10MBの画像を複数フォーマットで変換(5秒以上かかる)
  const imageBuffer = fs.readFileSync('/tmp/large-image.jpg');
  
  const webp = await sharp(imageBuffer).webp().toBuffer();
  const avif = await sharp(imageBuffer).avif().toBuffer();
  const thumb = await sharp(imageBuffer).resize(200, 200).toBuffer();
  
  res.status(200).json({ success: true });
}

After(修正後):

// api/processImage.js
import { Queue } from 'bullmq';
import redis from 'redis';

const connection = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

const imageQueue = new Queue('image-processing', { connection });

export default async function handler(req, res) {
  // 処理をキューに追加して即座に返す
  await imageQueue.add('convert', {
    imageId: req.body.imageId,
    formats: ['webp', 'avif', 'thumbnail']
  });
  
  res.status(202).json({ 
    message: 'Image processing queued',
    jobId: 'pending'
  });
}

別途ワーカープロセスで非同期に処理を実行し、Redisなどで進捗を管理します。

Vercel固有の注意点

Vercelのサーバーレス関数にはプランごとの実行時間制限のほか、メモリ上限(1024MB)やコールドスタート時間も性能に影響します。Hobby プラン利用時は以下の点に留意してください:

  • 実行時間上限:10秒(Proは300秒、Enterprise は3600秒)
  • メモリ上限:3008MB(共有リソース)
  • 同時実行数制限:1000(Hobby時は低い優先度)

また、Vercelのvercel logsコマンドでリアルタイムログを確認できます:

vercel logs <your-project-name> --follow

このログで関数の実際の実行時間を確認し、10秒に近づいていないか監視するとよいでしょう。

それでも解決しない場合

まず、Vercel Dashboardの「Deployments」タブで該当デプロイメントのログを確認してください。「Functions」セクションで実行時間の詳細が表示されます。以下のコマンドでローカルテストも有効です:

vercel dev

この環境ではタイムアウト上限なく関数を実行でき、実際の処理時間を計測できます。関数内にconsole.log()を仕込んで各処理の経過時間を記録し、ボトルネックを特定してください。

それでも原因不明の場合は、Vercelの公式ドキュメント(https://vercel.com/docs/functions/serverless-functions/limitations)を参照するか、Vercelサポートに問い合わせてください。特にエンタープライズ契約がある場合は、優先サポートが利用可能です。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。