beginner 9 min read

How to Set Up Cron Jobs in Next.js (Scheduled Tasks Guide)

Learn how to add cron jobs to your Next.js app. Covers Vercel Cron Jobs, node-cron for self-hosted, Inngest, and other scheduling options.

Prerequisites

  • Next.js 14+
  • Node.js 18+
  • Vercel account (for Vercel Cron) or self-hosted server

Cron Options for Next.js

Next.js doesn't have built-in cron support, but there are several approaches depending on your hosting:

  • Vercel Cron Jobs — Best for Vercel deployments, zero infrastructure
  • node-cron — For self-hosted Next.js with a custom server
  • Inngest / Trigger.dev — Third-party services with Next.js SDKs
  • External cron + API route — Any cron service calling your Next.js API

For Vercel deployments, Vercel Cron Jobs is the simplest and recommended approach.

Vercel Cron Jobs (Recommended)

Step 1: Create an API route handler

typescript
// app/api/cron/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const authHeader = request.headers.get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Your scheduled task
  await processExpiredSessions();

  return NextResponse.json({ success: true });
}

Step 2: Configure the schedule

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

Step 3: Set CRON_SECRET

Add a CRON_SECRET environment variable in Vercel project settings.

Deploy, and Vercel will call your API route on the schedule.

Self-Hosted: node-cron

For self-hosted Next.js apps (not on Vercel), use node-cron in a custom server or instrumentation file:

bash
npm install node-cron
typescript
// instrumentation.ts (Next.js 15+)
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const cron = await import('node-cron');

    cron.default.schedule('*/5 * * * *', async () => {
      console.log('Running scheduled task');
      await fetch(`${process.env.NEXT_PUBLIC_URL}/api/internal/cleanup`, {
        method: 'POST',
        headers: { Authorization: `Bearer ${process.env.INTERNAL_SECRET}` },
      });
    });
  }
}

Enable instrumentation in next.config.js:

javascript
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};

Note: This only works with a persistent Node.js process (not serverless).

API Route Examples

Cleanup expired data:

typescript
// app/api/cron/cleanup/route.ts
import { prisma } from '@/lib/prisma';

export async function GET(request: Request) {
  // Auth check omitted for brevity

  const deleted = await prisma.session.deleteMany({
    where: { expiresAt: { lt: new Date() } },
  });

  return Response.json({ deleted: deleted.count });
}

Send scheduled emails:

typescript
// app/api/cron/digest/route.ts
import { resend } from '@/lib/resend';

export async function GET(request: Request) {
  const users = await getDigestSubscribers();

  for (const user of users) {
    await resend.emails.send({
      to: user.email,
      subject: 'Your Weekly Digest',
      html: await renderDigest(user),
    });
  }

  return Response.json({ sent: users.length });
}

Common Cron Expressions

For Vercel Cron Jobs (standard 5-field syntax, UTC):

  • */5 * * * * — Every 5 minutes (Pro plan only)
  • 0 * * * * — Every hour
  • 0 0 * * * — Daily at midnight UTC
  • 0 9 * * 1-5 — Weekdays at 9 AM UTC
  • 0 0 1 * * — First day of every month

Hobby plan limit: Minimum frequency is once per day.

Every weekday at 9:00 AM

Next runs (UTC):

Mon, May 18, 2026 09:00

Tue, May 19, 2026 09:00

Wed, May 20, 2026 09:00

Alternative: Inngest

Inngest provides a developer-friendly SDK for scheduled functions in Next.js:

bash
npm install inngest
typescript
// inngest/functions.ts
import { inngest } from './client';

export const dailyCleanup = inngest.createFunction(
  { id: 'daily-cleanup' },
  { cron: '0 2 * * *' },
  async ({ step }) => {
    const count = await step.run('delete-expired', async () => {
      return await db.deleteExpiredRecords();
    });
    return { deleted: count };
  }
);

Inngest handles retries, logging, and provides a dashboard. Works with any hosting platform.

Debugging

Vercel Cron not running:

  • Check that vercel.json is in the project root
  • Cron only runs on production deployments
  • Verify the API route works: curl https://your-app.vercel.app/api/cron -H "Authorization: Bearer secret"
  • Check Vercel Logs in the dashboard

Self-hosted node-cron:

  • Ensure the instrumentation file is loaded (check terminal output)
  • Verify the process isn't restarting (PM2 or Docker can interfere)
  • Add logging to confirm the task fires

Production Tips

Always authenticate cron endpoints. Anyone who discovers your API route can trigger it.

Idempotent tasks: Vercel may retry failed invocations. Design tasks to be safe to run multiple times.

Function timeout: On Hobby plan, Vercel serverless functions time out at 10 seconds. Optimize your task or upgrade to Pro (300s) or Enterprise (900s).

Monitoring: Log task results and set up alerts:

typescript
export async function GET(request: Request) {
  const start = Date.now();
  try {
    const result = await runTask();
    console.log(`Cron completed in ${Date.now() - start}ms`, result);
    return Response.json(result);
  } catch (error) {
    console.error('Cron failed:', error);
    return Response.json({ error: 'Failed' }, { status: 500 });
  }
}

Frequently Asked Questions