Skip to content

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 function
import { 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 function
import { 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: after is 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.

  • A callback function which will be executed after the response (or prerender) is finished.

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.

  • after will be executed even if the response didn’t complete successfully. Including when an error is thrown or when notFound or redirect is called.
  • You can use React cache to deduplicate functions called inside after.
  • after can be nested inside other after calls, for example, you can create utility functions that wrap after calls to add additional functionality.

Whether you can use request APIs like cookies and headers inside after depends on where after is called from.

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' },
})
}

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.

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.

Deployment OptionSupported
Node.js serverYes
Docker containerYes
Static exportNo
AdaptersPlatform-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?.waitUntil

Which 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 use
const 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))
}
VersionChanges
v15.1.0after became stable.
v15.0.0-rcunstable_after introduced.