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.
API reference: afterRead docs ↗
Vercel Docsvercel.com
Module-level caching is especially effective because multiple concurrent requests share the same function instance.
Fluid computeRead docs ↗

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.