How to Run Cron Jobs in Next.js (With and Without Vercel)

Next.js has no built-in scheduler. Here are the three practical ways to run scheduled tasks in a Next.js app — Vercel Cron, an external scheduler, and node-cron — with code and trade-offs.

You built your app in Next.js, and now you need something to run on a schedule: send digest emails, clean up expired sessions, sync data from an external API, regenerate a sitemap.

Then you hit the wall every Next.js developer hits: Next.js has no scheduler. There's no next cron command. Serverless functions only run when something calls them.

The good news: the fix is simple. You expose the task as an API route, and something else calls that route on a schedule. The only real question is what that "something else" is. Let's go through the three options.

Step 1: Write the task as an API route

Whatever scheduler you pick, the task itself looks the same — a route handler that does the work:

// app/api/cron/cleanup/route.ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  // 1. Reject anyone who isn't your scheduler (see below)
  const auth = request.headers.get("authorization");
  if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  // 2. Do the actual work
  const deleted = await prisma.session.deleteMany({
    where: { expiresAt: { lt: new Date() } },
  });

  return NextResponse.json({ ok: true, deleted: deleted.count });
}

Two details matter here:

Option A: Vercel Cron

If you deploy on Vercel, you can declare schedules in vercel.json:

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

It's the zero-setup option, but know the limits before you rely on it:

That last point is the deal-breaker for anything important. Vercel Cron triggers your job; it doesn't monitor it.

Option B: An external scheduler (works anywhere)

The second option decouples scheduling from hosting: keep the API route, and let a dedicated cron service call it. This works identically on Vercel, Netlify, Railway, a VPS — anywhere your app has a URL.

With CronSpark the setup is: create a job, paste https://yourapp.com/api/cron/cleanup, pick a schedule (a cron expression or an interval down to 10 seconds), and add your Authorization header. Secrets are stored in an encrypted vault and injected into the header at request time, so they never sit in a dashboard field in plain text.

What you get over a platform-native trigger:

This is the setup we recommend for production apps: your code stays in your repo, your scheduling and monitoring live in one place outside your infrastructure — so it still works (and still alerts you) when your app is the thing that's down.

Option C: node-cron (only for long-running servers)

If you run Next.js as a persistent Node process on a VPS (next start behind nginx, or a custom server), you can schedule inside the process:

import cron from "node-cron";

cron.schedule("0 3 * * *", async () => {
  await cleanupExpiredSessions();
});

We covered this approach in depth in how to set up cron jobs in Node.js, but be aware of the failure modes:

If you go this route, at least add a heartbeat: ping a dead man's switch monitor at the end of each run, so a missed run becomes an alert instead of a mystery.

Which one should you pick?

The pattern to remember: in Next.js, scheduled work is just an HTTP endpoint plus something reliable that calls it — and "reliable" means it tells you when the call fails.