Vercel Cron Jobs が実行されない
vercel.json がプロジェクトルートに無い、path が存在しないルートを指している、Hobby プランの 1 日 1 回上限に当たっている、Production にデプロイしていない、の 4 系統が大半。
Project → Cron Jobs タブで実行履歴を確認する。
公開: 更新:
要約
Vercel Cron が動かないときは、ほぼ 4 つの確認で切り分けできる: vercel.json がルートにあるか、path が実在ルートを指しているか、プラン上限に当たっていないか、Production deployment があるか。
Project → Settings → Cron Jobs の Logs と次回実行時刻を見て、定義そのものが認識されているかから検証する。
よくある原因
vercel.jsonの配置: プロジェクトルート(monorepo なら Settings → General で設定した Root Directory 直下)に無いと Vercel に認識されない。- 存在しないルート:
path: "/api/cron"と書いても、対応する API Route が無ければ 404 で毎回失敗扱いになる。 - Hobby プランの上限: 1 ジョブ、1 日 1 回まで。
*/5 * * * *のような高頻度は Pro 以上が必要。 - Production 未デプロイ: Cron は Production 環境の最新デプロイに対して発火する。
Preview deploy しか無い状態では動かない。 CRON_SECRET検証失敗: 公式パターンのAuthorization: Bearer ${CRON_SECRET}検証が不整合だと、毎回 401 を返して失敗扱いになる。
解決策
1. vercel.json で crons を定義する
{
"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(新しいタブで開く) で、cron の本数は全プラン 1 プロジェクトあたり 100 本まで、Hobby は最小間隔が 1 日 1 回・起動時刻の精度は時間単位(±59 分)、と決まっている。
より高い頻度や分単位の精度が必要なら 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 の配置を疑う。