Keep handlers lean
Load static files at module level; run logging in after().
Next.js Docsnextjs.org
Use Next.js's after() to schedule work that should execute after a response is sent.
Vercel Docsvercel.com
Module-level caching is especially effective because multiple concurrent requests share the same function instance.
From vercel-react-best-practices: server-hoist-static-io and server-after-nonblocking.
An OG-image route under load
Fire a burst, then fire again. Try Cold start.
Read inside GETBad
GET: await readFile(font); await readFile(logo)Read at module levelGood
const fontData = fetch(…)import { ImageResponse } from 'next/og'
// Runs once per instance
const fontData = fetch(new URL('./fonts/Inter.ttf', import.meta.url))
.then((res) => res.arrayBuffer())
const logoData = fetch(new URL('./images/logo.png', import.meta.url))
.then((res) => res.arrayBuffer())
export async function GET(request: Request) {
const [font, logo] = await Promise.all([fontData, logoData])
return new ImageResponse(<Card logo={logo} />, {
fonts: [{ name: 'Inter', data: font }],
})
}
A POST that logs and notifies
Press Save. Watch when the client gets its answer.
Awaited inlineBad
await logUserAction(…)Scheduled with after()Good
after(() => logUserAction(…))import { after } from 'next/server'
import { headers, cookies } from 'next/headers'
export async function POST(request: Request) {
await updateDatabase(request)
after(async () => {
const userAgent = (await headers()).get('user-agent') || 'unknown'
const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
logUserAction({ sessionCookie, userAgent })
})
return Response.json({ status: 'success' })
}
What can live at module scope?
Pick where each asset should load.
0 / 8 right
Why it matters, pros and cons
Why it matters
Module code runs once per instance. after() sends the response before logging.
Pros
- Warm requests skip file reads entirely.
- Concurrent requests share one in-flight read.
- Clients wait only for work they need.
Cons
- Not for per-user, large or sensitive data.
- Runtime-edited files go stale; use a TTL cache.
after()saves no compute, and failures go unseen.