The Background Job Problem
Some operations shouldn't happen during a request:
- Sending welcome emails (users shouldn't wait for SMTP)
- Resizing images (CPU-intensive, blocks the response)
- Syncing data to external services
- Generating reports
Background jobs solve this. Here's how to implement them in Next.js.
Option 1: Vercel Edge Functions + Cron
For scheduled tasks:
// app/api/cron/daily-report/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(req: NextRequest) {
// Verify the request comes from Vercel Cron
const authHeader = req.headers.get('authorization')
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
await generateDailyReport()
return NextResponse.json({ success: true })
}
// vercel.json
{
"crons": [
{
"path": "/api/cron/daily-report",
"schedule": "0 9 * * *"
}
]
}
Option 2: Fire-and-Forget (Simple Background Task)
For operations after responding to the user:
// app/api/users/route.ts
export async function POST(req: NextRequest) {
const { email, name } = await req.json()
// Create user synchronously
const user = await db.user.create({ data: { email, name } })
// Send email asynchronously -- don't await
// Vercel will keep the function alive for background work
sendWelcomeEmail(user.email, user.name).catch(err => {
logger.error({ err, userId: user.id }, 'Failed to send welcome email')
})
// Respond immediately
return NextResponse.json({ user })
}
Option 3: Trigger.dev (Full Job Queue)
For complex, retriable background jobs:
npm install @trigger.dev/sdk @trigger.dev/nextjs
// jobs/send-welcome-email.ts
import { client } from '@/trigger'
import { eventTrigger } from '@trigger.dev/sdk'
import { z } from 'zod'
client.defineJob({
id: 'send-welcome-email',
name: 'Send Welcome Email',
version: '0.0.1',
trigger: eventTrigger({
name: 'user.created',
schema: z.object({ userId: z.string(), email: z.string() })
}),
run: async (payload, io) => {
await io.runTask('send-email', async () => {
await sendWelcomeEmail(payload.email)
})
await io.runTask('provision-resources', async () => {
await createDefaultWorkspace(payload.userId)
})
}
})
// Trigger from API route
await client.sendEvent({
name: 'user.created',
payload: { userId: user.id, email: user.email }
})
Option 4: QStash (Vercel-Native Job Queue)
import { Client } from '@upstash/qstash'
const qstash = new Client({ token: process.env.QSTASH_TOKEN! })
// Queue a job from any API route
await qstash.publishJSON({
url: `${process.env.NEXTAUTH_URL}/api/jobs/process-payment`,
body: { userId, amount, currency },
delay: 0,
retries: 3,
})
// The job handler
// app/api/jobs/process-payment/route.ts
import { verifySignatureAppRouter } from '@upstash/qstash/nextjs'
export const POST = verifySignatureAppRouter(async (req) => {
const { userId, amount } = await req.json()
await processPayment(userId, amount)
return NextResponse.json({ success: true })
})
When to Use Each
Vercel Cron:
Best for: scheduled tasks (daily reports, cleanup jobs)
Limit: 1 per minute minimum interval (free), every second (pro)
Fire-and-forget:
Best for: simple notifications, logging, analytics
Risk: if function times out, job is lost
Trigger.dev:
Best for: complex workflows, retries, observability
Best for: when you need to see job history and debug failures
QStash:
Best for: Vercel deployments, simple delayed jobs, webhooks
Best for: when you're already using Upstash Redis
Ship Background Jobs Pre-Configured
The AI SaaS Starter Kit includes fire-and-forget email sending and Vercel cron setup for scheduled tasks.
$99 one-time at whoffagents.com
Top comments (0)