The after() API in Next.js 16: Non-Blocking Work After the Response
after() lets you run callbacks once the response is sent — perfect for logging LLM usage, writing audit trails, and firing analytics without slowing down your AI-native app.
5 min read
Every AI-native app eventually needs to do work that shouldn't block the user: log an LLM call to your observability platform, write a usage record to your database, fire an analytics event after a streaming response finishes. Before Next.js introduced after, the options were awkward — fire-and-forget promises that could silently fail, or blocking the response while you waited for a side-effect that the user never needed to see.
after, stable since Next.js 15.1 and available throughout Next.js 16, gives you a clean primitive for this: schedule a callback, and the framework guarantees it runs once the response is sent.
The basic shape
Import after from next/server and call it anywhere in a Server Component, Route Handler, or Server Function:
import { after } from 'next/server'
import { recordUsage } from '@/lib/db'
export default async function Page() {
const result = await runAIPipeline()
after(async () => {
await recordUsage({ tokens: result.usage.total_tokens })
})
return <Output result={result} />
}The callback runs after the response has been flushed to the browser. The user sees the page immediately; the database write happens in the background. If the callback throws, it does not affect the already-sent response.
Why this matters for AI apps specifically
LLM API calls produce metadata you want to record — token counts, model IDs, latency, tool calls invoked — but that metadata is only available once the call completes. In a streaming response, that moment coincides with the last chunk being flushed. after fires exactly here: after streaming ends, before the serverless function terminates.
Practical uses across a typical AI-native stack:
- Token accounting: write token usage to a per-user quota table without holding the response open.
- Observability: emit a span to your tracing backend (LangSmith, Honeycomb, Datadog) with the full prompt, completion, and timing data.
- Caching warm-up: after serving a cache miss, populate a KV store so the next request is fast.
- Audit trails: log which user triggered which AI action, without blocking the action itself.
Reading request data correctly
There is one constraint worth understanding. In Server Components, you cannot call cookies() or headers() inside the aftercallback — those APIs are only available during the component's render phase. Read them before the callback and capture the values in a closure:
import { after } from 'next/server'
import { headers } from 'next/headers'
export default async function Page() {
// Read during render — allowed
const requestId = (await headers()).get('x-request-id') ?? 'unknown'
after(async () => {
// Use the captured value — correct
await logRequest({ requestId })
// DO NOT call headers() here — throws at runtime
})
return <Dashboard />
}In Route Handlers and Server Functions the restriction does not apply — you can call cookies() and headers() freely inside the callback, because those contexts always run dynamically.
Combining with Partial Prerendering
If you are using PPR (a static shell with dynamic holes), place the after call in the dynamic component that lives inside <Suspense>, not in the outer static shell. The callback will fire once that dynamic segment renders and its subtree is flushed, which is exactly when you have the request-scoped data you need.
Serverless and platform notes
On Vercel, after is backed by waitUntil, which extends the function lifetime until the callback settles. On a self-hosted Node.js server the same guarantee holds. On platforms that do not implement waitUntil, the callback may be cut off if the process exits before it completes — check your platform's adapter documentation if you are not on Vercel or a standard Node server.
The pattern in one sentence
Put everything the user needs to see inside the component return; put everything you need to record about the request inside after. The response stays fast, the side effects are reliable, and neither concern leaks into the other.