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
In this guide
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
// 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
// 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:
npm install node-cron// 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:
module.exports = {
experimental: {
instrumentationHook: true,
},
};Note: This only works with a persistent Node.js process (not serverless).
API Route Examples
Cleanup expired data:
// 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:
// 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 hour0 0 * * *— Daily at midnight UTC0 9 * * 1-5— Weekdays at 9 AM UTC0 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:
npm install inngest// 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.jsonis 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:
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 });
}
}