できない.dev

Vercel Cron Jobs が実行されない

`vercel.json` がプロジェクトルートに無い、`path` が存在しないルートを指している、Hobby プランの 1 日 1 回上限に当たっている、Production にデプロイしていない、の 4 系統が大半。
Project → Cron Jobs タブで実行履歴を確認する。

#vercel#cron#schedule#deploy

公開:

要約

Vercel Cron が動かないときは、ほぼ 4 つの確認で切り分けできる: vercel.json がルートにあるかpath が実在ルートを指しているかプラン上限に当たっていないかProduction deployment があるか
Project → Settings → Cron Jobs の Logs と次回実行時刻を見て、定義そのものが認識されているかから検証する。

よくある原因

  1. vercel.json の配置: プロジェクトルート(monorepo なら Settings → General で設定した Root Directory 直下)に無いと Vercel に認識されない。
  2. 存在しないルート: path: "/api/cron" と書いても、対応する API Route が無ければ 404 で毎回失敗扱いになる。
  3. Hobby プランの上限: 1 ジョブ、1 日 1 回まで。*/5 * * * * のような高頻度は Pro 以上が必要。
  4. Production 未デプロイ: Cron は Production 環境の最新デプロイに対して発火する。
    Preview deploy しか無い状態では動かない。
  5. CRON_SECRET 検証失敗: 公式パターンの Authorization: Bearer ${CRON_SECRET} 検証が不整合だと、毎回 401 を返して失敗扱いになる。

解決策

1. vercel.jsoncrons を定義する

{
  "crons": [
    {
      "path": "/api/cron",
      "schedule": "0 0 * * *"
    }
  ]
}

公式の Cron Jobs ドキュメント のとおり、schedule は 5 フィールドの cron 式(秒は含まない)。
時刻は UTC で評価される。

2. API Route / Route Handler を作る

// app/api/cron/route.ts
import { NextRequest } from "next/server";
 
export async function GET(req: NextRequest) {
  const auth = req.headers.get("authorization");
  if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response("Unauthorized", { status: 401 });
  }
  await doScheduledJob();
  return Response.json({ ok: true });
}

GET に 200 を返すのが基本。CRON_SECRET は Project → Environment Variables で Production にだけ登録する。

3. プランを確認する

Usage and Pricing で、Hobby は 1 アカウントあたり 2 cron まで・1 日 1 回まで・最大 60 秒、と決まっている。
それ以上の頻度や本数が必要なら Pro 以上へ。

4. Production にデプロイして Logs を見る

git push origin main          # Production deploy をトリガ
npx vercel logs --follow      # 実行ログを stream で確認

Project → Settings → Cron Jobs の Next run 時刻と Logs を確認する(Manage Cron Jobs)。
Logs に何も出ていなければ定義そのものが認識されていないので、vercel.json の配置を疑う。

この記事は役立ちましたか?