after
Source URL: https://nextjs.org/docs/app/api-reference/functions/after
after allows you to schedule work to be executed after a response (or prerender) is finished. This is useful for tasks and other side effects that should not block the response, such as logging and analytics.
It can be used in Server Components (including generateMetadata), Server Functions, Route Handlers, and Proxy.
The function accepts a callback that will be executed after the response (or prerender) is finished:
import { after } from 'next/server'// Custom logging functionimport { log } from '@/app/utils'
export default function Layout({ children }: { children: React.ReactNode }) { after(() => { // Execute after the layout is rendered and sent to the user log() }) return <>{children}</>}import { after } from 'next/server'// Custom logging functionimport { log } from '@/app/utils'
export default function Layout({ children }) { after(() => { // Execute after the layout is rendered and sent to the user log() }) return <>{children}</>}Good to know:
afteris not a Dynamic API and calling it does not cause a route to become dynamic. If it’s used within a static page, the callback will execute at build time, or whenever a page is revalidated.
Reference
Section titled “Reference”Parameters
Section titled “Parameters”- A callback function which will be executed after the response (or prerender) is finished.
Duration
Section titled “Duration”after will run for the platform’s default or configured max duration of your route. If your platform supports it, you can configure the timeout limit using the maxDuration route segment config.
Good to know
Section titled “Good to know”afterwill be executed even if the response didn’t complete successfully. Including when an error is thrown or whennotFoundorredirectis called.- You can use React
cacheto deduplicate functions called insideafter. aftercan be nested inside otheraftercalls, for example, you can create utility functions that wrapaftercalls to add additional functionality.
Examples
Section titled “Examples”With request APIs
Section titled “With request APIs”Whether you can use request APIs like cookies and headers inside after depends on where after is called from.
In Route Handlers and Server Functions
Section titled “In Route Handlers and Server Functions”You can call cookies and headers directly inside the after callback when used in Route Handlers and Server Functions. This is useful for logging activity after a mutation or API request. For example:
import { after } from 'next/server'import { cookies, headers } from 'next/headers'import { logUserAction } from '@/app/utils'
export async function POST(request: Request) { // Perform mutation // ...
// Log user activity for analytics after(async () => { const userAgent = (await headers()).get('user-agent') || 'unknown' const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
logUserAction({ sessionCookie, userAgent }) })
return new Response(JSON.stringify({ status: 'success' }), { status: 200, headers: { 'Content-Type': 'application/json' }, })}import { after } from 'next/server'import { cookies, headers } from 'next/headers'import { logUserAction } from '@/app/utils'
export async function POST(request) { // Perform mutation // ...
// Log user activity for analytics after(async () => { const userAgent = (await headers()).get('user-agent') || 'unknown' const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
logUserAction({ sessionCookie, userAgent }) })
return new Response(JSON.stringify({ status: 'success' }), { status: 200, headers: { 'Content-Type': 'application/json' }, })}In Server Components (pages and layouts)
Section titled “In Server Components (pages and layouts)”Server Components (including pages, layouts, and generateMetadata) cannot use cookies, headers, or other Dynamic APIs inside after. This is because Next.js needs to know which part of the component tree accesses request data to support Partial Prerendering and Cache Components, but after runs after React’s rendering lifecycle.
If you need request data inside an after callback in a Server Component, read it beforehand and pass the values in:
import { after } from 'next/server'import { cookies, headers } from 'next/headers'import { logUserAction } from '@/app/utils'
export default async function Page() { // Read request data before `after` — this is allowed // These calls will be read during the component's rendering lifecycle const userAgent = (await headers()).get('user-agent') || 'unknown' const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
after(() => { // Use the values read above logUserAction({ sessionCookie, userAgent }) })
return <h1>My Page</h1>}import { after } from 'next/server'import { cookies, headers } from 'next/headers'import { logUserAction } from '@/app/utils'
export default async function Page() { // Read request data before `after` — this is allowed // These calls will be read during the component's rendering lifecycle const userAgent = (await headers()).get('user-agent') || 'unknown' const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
after(() => { // Use the values read above logUserAction({ sessionCookie, userAgent }) })
return <h1>My Page</h1>}Calling cookies() or headers() inside the after callback in a Server Component will throw a runtime error.
With Cache Components
Section titled “With Cache Components”When using Cache Components, components that access request data like cookies or headers must be wrapped in <Suspense> so the rest of the page can be prerendered into a static shell.
You can combine this pattern with after by reading request data in a dynamic component and passing it into after:
import { Suspense } from 'react'import { after } from 'next/server'import { cookies } from 'next/headers'import { logUserAction } from '@/app/utils'
export default function Page() { return ( <> <h1>Part of the static shell</h1> <Suspense fallback={<p>Loading...</p>}> <DynamicContent /> </Suspense> </> )}
async function DynamicContent() { const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
// Schedule work after the response is sent after(() => { logUserAction({ sessionCookie }) })
return <p>Your session: {sessionCookie}</p>}import { Suspense } from 'react'import { after } from 'next/server'import { cookies } from 'next/headers'import { logUserAction } from '@/app/utils'
export default function Page() { return ( <> <h1>Part of the static shell</h1> <Suspense fallback={<p>Loading...</p>}> <DynamicContent /> </Suspense> </> )}
async function DynamicContent() { const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
// Schedule work after the response is sent after(() => { logUserAction({ sessionCookie }) })
return <p>Your session: {sessionCookie}</p>}In this example, <h1> and the <Suspense> fallback are included in the static shell. DynamicContent reads the cookie during rendering and passes it into after via closure. Since cookies() is called outside the after callback (during the component’s render), this works correctly.
Platform Support
Section titled “Platform Support”| Deployment Option | Supported |
|---|---|
| Node.js server | Yes |
| Docker container | Yes |
| Static export | No |
| Adapters | Platform-specific |
Learn how to configure after when self-hosting Next.js.
Reference: supporting `after` for serverless platforms
Using after in a serverless context requires waiting for asynchronous tasks to finish after the response has been sent. In Next.js and Vercel, this is achieved using a primitive called waitUntil(promise), which extends the lifetime of a serverless invocation until all promises passed to waitUntil have settled.
If you want your users to be able to run after, you will have to provide your implementation of waitUntil that behaves in an analogous way.
When after is called, Next.js will access waitUntil like this:
const RequestContext = globalThis[Symbol.for('@next/request-context')]const contextValue = RequestContext?.get()const waitUntil = contextValue?.waitUntilWhich means that globalThis[Symbol.for('@next/request-context')] is expected to contain an object like this:
type NextRequestContext = { get(): NextRequestContextValue | undefined}
type NextRequestContextValue = { waitUntil?: (promise: Promise<any>) => void}Here is an example of the implementation.
import { AsyncLocalStorage } from 'node:async_hooks'
const RequestContextStorage = new AsyncLocalStorage<NextRequestContextValue>()
// Define and inject the accessor that next.js will useconst RequestContext: NextRequestContext = { get() { return RequestContextStorage.getStore() },}globalThis[Symbol.for('@next/request-context')] = RequestContext
const handler = (req, res) => { const contextValue = { waitUntil: YOUR_WAITUNTIL } // Provide the value return RequestContextStorage.run(contextValue, () => nextJsHandler(req, res))}Version History
Section titled “Version History”| Version | Changes |
|---|---|
v15.1.0 | after became stable. |
v15.0.0-rc | unstable_after introduced. |