⚠️ Be Careful with Long-Running Actions in Next.js Route Handlers (`route.tsx`)
Mike Codeur
⚠️ Be Careful with Long-Running Actions in Next.js Route Handlers (route.tsx
)
When you're building a route in Next.js 14/15 using app/route.tsx
, it's tempting to include business logic (tracking, logging, analytics, etc.) right before returning a response. A common example:
export async function GET() {
await trackClick()
return NextResponse.redirect('https://other-site.com')
}
But this pattern can quickly become a problem…
😬 The Problem: You're Blocking the HTTP Response
In this case, the user has to wait for trackClick()
to complete before being redirected. If this takes 200ms or more, you're delaying the response for no reason.
Worse:
🧨 If You Try to "Optimize" by Removing await
:
void trackClick()
return NextResponse.redirect('https://other-site.com')
➡️ It might look like you've improved performance, but the process may be killed right after return
(especially on Vercel edge/serverless). The result?
Your trackClick()
might never run or run inconsistently.
✅ The Right Way: after()
or waitUntil()
🧠 In an App Route (Next.js 15.1+):
import { after } from 'next/server'
export async function GET() {
after(async () => {
try {
await trackClick()
} catch (e) {
console.error('Tracking failed', e)
}
})
return NextResponse.redirect('https://other-site.com')
}
✅ The code inside after()
will run after the response is sent (or after prerendering). It won’t block the user and won’t be cut off unexpectedly. It’s perfect for logging, tracking, or other side effects that don’t affect the main response.
💡 In a Vercel Function:
typescript
import { waitUntil } from '@vercel/functions'
async function trackClick() {
const res = await fetch('https://my-analytics-service.com/track')
return res.json()
}
export function GET(request: Request) {
waitUntil(trackClick().then((json) => console.log('Track response', json)))
return new Response('Redirect complete')
}
✅ waitUntil()
lets you execute background tasks after the response is sent, safely and without blocking.
🧪 Real-World Example:
Using console.time()
on Vercel, I measured:
- With
await
inside the route: 400+ ms execution - With
after()
orwaitUntil()
: response sent in <300ms, tracking continues safely afterward
🚀 TL;DR:
- Never put long operations (DB writes, logs, analytics) before your
return
if you want fast responses void
withoutawait
≠ safe in edge/serverless environments- Use
after()
(Next.js 15.1+) orwaitUntil()
(Vercel Functions) to ensure background code runs without blocking the user or being killed early
It’s a small detail—but this kind of detail makes all the difference when you're building fast, reliable tools ⚡️