Skip to main content
doc0blade47/next.jsdocumentation home
Sign in

Server Actions

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

Server Actions enable secure remote procedure calls from client-side components to server-side functions by orchestrating compile-time manifest generation, encrypted bound argument serialization, and structured HTTP dispatch.

During compilation, bundler plugins record Server Actions into manifests that map action identifiers to server modules packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1110-1162. On the client, invocations flow through callServer into a sequential action queue [[packages/next/src/client/app-call-server.ts:1-16], [packages/next/src/client/sequential-call-server.ts:1-28]]. The server identifies these requests via specific action headers and validates origin headers against cross-site request forgery policies [[packages/next/src/server/app-render/action-handler.ts:1522-1529], [packages/next/src/server/lib/server-action-request-meta.ts:6-14]]. Once authenticated, the server executes the action, synchronizes mutated cookies, and prepares a Flight render response packages/next/src/server/app-render/action-handler.ts:1473-1515. Finally, the client processes the resulting payload through the serverActionReducer to update router state, invalidate prefetch and bfcache entries, and re-ping visible links packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:317-366.

Action registration and manifest generation

Manifest compilation and asset emission

During compilation, the bundler identifies and collects Server Actions into structured manifests that map action identifiers to their respective server modules and worker entry points.

The compilation process traverses modules via createActionAssets inside the Webpack plugin to record action entry points for both standard Node.js server and Edge server runtimes packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1110-1113. By inspecting modules matching /next-flight-action-entry-loader/, the plugin determines whether an action originated from a client component (&__client_imported__=true) or a server component packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1116-1122.

traverseModules(compilation, (mod, _chunk, chunkGroup, modId) => {
  // Go through all action entries and record the module ID for each entry.
  if (
    chunkGroup.name &&
    mod.request &&
    modId &&
    /next-flight-action-entry-loader/.test(mod.request)
  ) {
    const fromClient = /&__client_imported__=true/.test(mod.request)
 
    const mapping = this.isEdgeServer
      ? pluginState.edgeServerActionModules
      : pluginState.serverActionModules
 
    if (!mapping[chunkGroup.name]) {
      mapping[chunkGroup.name] = {}
    }
 
    mapping[chunkGroup.name][fromClient ? 'client' : 'server'] = {
      moduleId: modId,
      async: compilation.moduleGraph.isAsync(mod),
    }
  }

Sources: packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1114-1136

Note

Server actions imported from client components use the actionBrowser Webpack layer (__client_imported__=true), whereas server-defined actions retain the standard server layer. This distinction ensures worker resolution maps to the correct client or server module identifier (moduleId).

The plugin populates both Node.js and Edge server action manifests with encryption keys and serialized worker mappings before emitting asset files into the compilation output packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1139-1190.

  const serverManifest = {
    node: serverActions,
    edge: edgeServerActions,
    encryptionKey: this.encryptionKey,
  }
  const edgeServerManifest = {
    ...serverManifest,
    encryptionKey: 'process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY',
  }
 
  const json = JSON.stringify(serverManifest, null, this.dev ? 2 : undefined)
  const edgeJson = JSON.stringify(
    edgeServerManifest,
    null,
    this.dev ? 2 : undefined
  )
 
  compilation.emitAsset(
    `${this.assetPrefix}${SERVER_REFERENCE_MANIFEST}.js`,
    new sources.RawSource(
      `self.__RSC_SERVER_MANIFEST=${JSON.stringify(edgeJson)}`
    ) as unknown as webpack.sources.RawSource
  )
  compilation.emitAsset(
    `${this.assetPrefix}${SERVER_REFERENCE_MANIFEST}.json`,
    new sources.RawSource(json) as unknown as webpack.sources.RawSource
  )
}

Sources: packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1163-1190

Runtime module mapping and singletons

At runtime, the server utilizes a global singleton (Symbol.for('next.server.manifests')) to manage server actions, module maps, and client reference manifests without relying on asynchronous storage [[packages/next/src/server/app-render/manifests-singleton.ts:56-60], [packages/next/src/server/app-render/manifests-singleton.ts:72-83]].

export interface ServerModuleMap {
  readonly [name: string]: {
    readonly id: string | number
    readonly name: string
    readonly chunks: Readonly<Array<string>> // currently not used
    readonly async?: boolean
  }
}

Sources: packages/next/src/server/app-render/manifests-singleton.ts:16-23

Warning

Server reference ID validation enforces strict logging caps. Identifiers exceeding MAX_LOGGED_SERVER_REFERENCE_ID_LENGTH (100 characters) are truncated to TRUNCATED_SERVER_REFERENCE_ID_LENGTH (90 characters) and appended with an ellipsis (…) inside getInvalidServerReferenceIdError to prevent log flooding and log-line forgery via control characters packages/next/src/server/app-render/manifests-singleton.ts:31-54.

If an requested action ID cannot be located within the active manifest during execution, getActionNotFoundError constructs a standard failure indicating a potential version mismatch across deployments packages/next/src/server/app-render/manifests-singleton.ts:25-29.

export function getActionNotFoundError(actionId: string | null): Error {
  return new Error(
    `Failed to find Server Action${actionId ? ` "${actionId}"` : ''}. This request might be from an older or newer deployment.\nRead more: https://nextjs.org/docs/messages/failed-to-find-server-action`
  )
}

Sources: packages/next/src/server/app-render/manifests-singleton.ts:25-29

Client action invocation and dispatch

Server Actions are invoked on the client via the callServer entry point function defined in app-call-server.ts. By default, calls are routed through the sequential implementation located in sequential-call-server.ts, wrapping the action execution inside a React startTransition and dispatching an ACTION_SERVER_ACTION payload [[packages/next/src/client/sequential-call-server.ts:1-28], [packages/next/src/client/app-call-server.ts:1-16]]. When experimental.concurrentRouterQueue is enabled, compiler aliases dynamically swap this implementation for concurrent-call-server.ts [[packages/next/src/client/sequential-call-server.ts:1-6], [packages/next/src/client/app-call-server.ts:1-8]].

export async function callServer(actionId: string, actionArgs: any[]) {
  return new Promise((resolve, reject) => {
    startTransition(() => {
      dispatchAppRouterAction({
        type: ACTION_SERVER_ACTION,
        actionId,
        actionArgs,
        resolve,
        reject,
      })
    })
  })

Sources: packages/next/src/client/sequential-call-server.ts:17-28

Dispatched actions enter the router action queue via dispatchAction inside app-router-instance.ts. Non-restore actions create a deferred promise whose resolve and reject handlers are attached to a new ActionQueueNode packages/next/src/client/components/app-router-instance.ts:152-184. If the queue is empty (actionQueue.pending === null), the action becomes the last node and is executed immediately via runAction packages/next/src/client/components/app-router-instance.ts:186-196. If a navigation or back/forward action (ACTION_NAVIGATE or ACTION_RESTORE) arrives while an action is pending, it preempts the queue by marking the active node as discarded and updating pointers packages/next/src/client/components/app-router-instance.ts:197-210.

Warning

When a pending server action is discarded by an incoming navigation, its state changes are suppressed. However, if the discarded action previously revalidated data (action.payload.didRevalidate), actionQueue.needsRefresh is flagged true so that a soft refresh (ACTION_REFRESH) is automatically flushed once the queue drains completely packages/next/src/client/components/app-router-instance.ts:112-128.

function handleResult(nextState: AppRouterState) {
  // if we discarded this action, the state should also be discarded
  if (action.discarded) {
    // Check if the discarded server action revalidated data
    if (
      action.payload.type === ACTION_SERVER_ACTION &&
      action.payload.didRevalidate
    ) {
      // The server action was discarded but it revalidated data,
      // mark that we need to refresh after all actions complete
      actionQueue.needsRefresh = true
    }
    // This can't advance the queue (this action is no longer its head), but
    // if the queue has already drained, it flushes the refresh now.
    runRemainingActions(actionQueue, action, setState)
    return
  }

Sources: packages/next/src/client/components/app-router-instance.ts:112-128

The router instance maintains state queues, nodes, and execution helpers that manage promise resolution and error handling across asynchronous action boundaries packages/next/src/client/components/app-router-instance.ts:30-150.

Type / FunctionPurpose
AppRouterActionQueueHolds the canonical state, dispatch function, action reducer, and pointers for pending, last, and flags (needsRefresh, wasPreempted).
ActionQueueNodeRepresents an item in the queue containing payload, next pointer, deferred resolve/reject callbacks, and a discarded boolean flag.
runActionExecutes the current action payload against prevState, wrapping promise resolution or triggering handleError and handleResult.
runRemainingActionsAdvances actionQueue.pending to settledAction.next, transitions final states when preemption clears, or flushes pending refreshes.
dispatchActionCreates deferred execution promises for asynchronous payloads, wraps updates in startTransition, and handles queue preemption logic for navigations.

Sources: packages/next/src/client/components/app-router-instance.ts:30-210

Request detection and CSRF protection

Server-side identification of incoming Server Actions relies on inspecting request metadata. The utility function getServerActionRequestMetadata inspects request headers and method to classify incoming HTTP transactions packages/next/src/server/lib/server-action-request-meta.ts:6-14. It extracts the action ID from the next-action header and examines the content-type header to determine whether the request is a fetch-based action, a multipart form action, or a URL-encoded form action packages/next/src/server/lib/server-action-request-meta.ts:16-44.

export function getServerActionRequestMetadata(
  req: IncomingMessage | BaseNextRequest | NextRequest
): {
  actionId: string | null
  isURLEncodedAction: boolean
  isMultipartAction: boolean
  isFetchAction: boolean
  isPossibleServerAction: boolean
} {
  let actionId: string | null
  let contentType: string | null
 
  if (req.headers instanceof Headers) {
    actionId = req.headers.get(ACTION_HEADER) ?? null
    contentType = req.headers.get('content-type')
  } else {
    actionId = (req.headers[ACTION_HEADER] as string) ?? null
    contentType = req.headers['content-type'] ?? null
  }
 
  // We don't actually support URL encoded actions, and the action handler will bail out if it sees one.
  // But we still want it to flow through to the action handler, to prevent changes in behavior when a regular
  // page component tries to handle a POST.
  const isURLEncodedAction = Boolean(
    req.method === 'POST' && contentType === 'application/x-www-form-urlencoded'
  )
  const isMultipartAction = Boolean(
    req.method === 'POST' && contentType?.startsWith('multipart/form-data')
  )
  const isFetchAction = Boolean(
    actionId !== undefined &&
      typeof actionId === 'string' &&
      req.method === 'POST'
  )
 
  const isPossibleServerAction = Boolean(
    isFetchAction || isURLEncodedAction || isMultipartAction
  )
 
  return {
    actionId,
    isURLEncodedAction,
    isMultipartAction,
    isFetchAction,
    isPossibleServerAction,
  }
}

Sources: packages/next/src/server/lib/server-action-request-meta.ts:6-52

Metadata PropertyEvaluation RulePurpose
actionIdreq.headers.get(ACTION_HEADER) ?? nullExtracts the target action identifier header (next-action).
isURLEncodedActionreq.method === 'POST' && contentType === 'application/x-www-form-urlencoded'Identifies URL-encoded form posts (rejected by the action handler).
isMultipartActionreq.method === 'POST' && contentType?.startsWith('multipart/form-data')Identifies multipart form action submissions.
isFetchActionactionId !== undefined && typeof actionId === 'string' && req.method === 'POST'Identifies standard fetch-based Server Action calls.
isPossibleServerActionisFetchAction || isURLEncodedAction || isMultipartActionMaster boolean indicating any candidate Server Action request.

Sources: packages/next/src/server/lib/server-action-request-meta.ts:15-51

To protect against Cross-Site Request Forgery (CSRF), Next.js validates that request origins match allowed patterns packages/next/src/server/app-render/csrf-protection.ts:72-94. The function isCsrfOriginAllowed normalizes origin domain strings to lowercase ASCII per RFC 1035 and evaluates them against configured allowed origins, which may include literal matches or wildcard subdomains evaluated by matchWildcardDomain packages/next/src/server/app-render/csrf-protection.ts:6-94.

Caution

Wildcard domain patterns are strictly restricted from matching entire domains or root TLDs (* or ** alone or matching .com). Wildcards can only match subdomain segments; patterns where patternParts.length === 1 and the part is * or ** automatically return false to prevent open cross-site vulnerabilities packages/next/src/server/app-render/csrf-protection.ts:25-32.

export const isCsrfOriginAllowed = (
  originDomain: string,
  allowedOrigins: string[] = []
): boolean => {
  // DNS names are case-insensitive per RFC 1035
  // Use ASCII-only toLowerCase to avoid unicode issues
  const normalizedOrigin = originDomain.replace(/[A-Z]/g, (c) =>
    c.toLowerCase()
  )
 
  return allowedOrigins.some((allowedOrigin) => {
    if (!allowedOrigin) return false
 
    const normalizedAllowed = allowedOrigin.replace(/[A-Z]/g, (c) =>
      c.toLowerCase()
    )
 
    return (
      normalizedAllowed === normalizedOrigin ||
      matchWildcardDomain(originDomain, allowedOrigin)
    )
  })
}

Sources: packages/next/src/server/app-render/csrf-protection.ts:72-94

Before executing multi-part or form-encoded actions, areAllActionIdsValid pre-parses form data to verify that all referenced action ID fields and descriptors match entries present in the server module map packages/next/src/server/app-render/action-handler.ts:1558-1606. It enforces limits on bound argument descriptors and restricts the maximum number of form reference fields ($ACTION_REF_) to at most two per submission packages/next/src/server/app-render/action-handler.ts:1579-1585.

function areAllActionIdsValid(
  mpaFormData: FormData,
  serverModuleMap: ServerModuleMap
): boolean {
  let seenActionRefs = 0
  let hasAtLeastOneAction = false
  // Before we attempt to decode the payload for a possible MPA action, assert
  // that all action IDs are valid IDs.
  for (let key of mpaFormData.keys()) {
    if (!key.startsWith($ACTION_)) {
      // not a relevant field
      continue
    }
 
    if (key.startsWith($ACTION_ID_)) {
      // No Bound args case
      if (isInvalidActionIdFieldName(key, serverModuleMap)) {
        return false
      }
 
      hasAtLeastOneAction = true
    } else if (key.startsWith($ACTION_REF_)) {
      if (++seenActionRefs > 2) {
        // We only expect to see at most 2 $ACTION_REF_ fields in the form data:
        // one from <form action="..." method="post">
        // and one from <input action="..." type="submit">
        return false
      }
 
      // Bound args case
      const actionDescriptorField =
        $ACTION_ + key.slice($ACTION_REF_.length) + ':0'
      const actionFields = mpaFormData.getAll(actionDescriptorField)
      if (actionFields.length !== 1) {
        return false
      }
      const actionField = actionFields[0]
      if (typeof actionField !== 'string') {
        return false
      }
 
      if (isInvalidStringActionDescriptor(actionField, serverModuleMap)) {
        return false
      }
      hasAtLeastOneAction = true
    }
  }
  return hasAtLeastOneAction
}

Sources: packages/next/src/server/app-render/action-handler.ts:1558-1606

Server action execution and rendering

Action resolution and argument limits

When an incoming request is identified as a Server Action, the handler resolves the target action identifier through getActionModIdOrError, inspecting the next-action header and looking up the entry in the serverModuleMap packages/next/src/server/app-render/action-handler.ts:1522-1547. If the action ID header is missing, an invariant error is raised packages/next/src/server/app-render/action-handler.ts:1527-1529. If the module entry is null, the function distinguishes between a malformed server reference ID and a missing action using mightBeServerReferenceId, throwing either getActionNotFoundError or getInvalidServerReferenceIdError packages/next/src/server/app-render/action-handler.ts:1533-1544.

function getActionModIdOrError(
  actionId: string | null,
  serverModuleMap: ServerModuleMap
): string | number {
  // if we're missing the action ID header, we can't do any further processing
  if (!actionId) {
    throw new InvariantError("Missing 'next-action' header.")
  }
 
  const entry = serverModuleMap[actionId]
 
  if (entry == null) {
    // The proxy throws for malformed IDs and IDs that are missing from the
    // manifest. It only returns undefined when the ID collides with a
    // well-known property name (e.g. `toString`) that the proxy excludes from
    // server reference validation so that framework reflection probes don't
    // throw. Next.js never produces such an ID, so this is almost certainly a
    // probe with a known-bad ID. Repeat the manifest's throw logic here so the
    // caller gets a diagnosable error instead of a `TypeError`.
    throw mightBeServerReferenceId(actionId)
      ? getActionNotFoundError(actionId)
      : getInvalidServerReferenceIdError(actionId)
  }
 
  return entry.id
}

Sources: packages/next/src/server/app-render/action-handler.ts:1522-1547

Execution proceeds inside executeActionAndPrepareForRender, which transitions the request store phase to 'action' packages/next/src/server/app-render/action-handler.ts:1473. To prevent stack overflow vulnerabilities from malicious requests during action.apply(), the argument list length is validated against a strict ceiling packages/next/src/server/app-render/action-handler.ts:1456-1480.

 * Limit on the number of arguments passed to a server action. This prevents
 * stack overflow during `action.apply()` from malicious requests.
 */
const SERVER_ACTION_ARGS_LIMIT = 1000
 
async function executeActionAndPrepareForRender<
  TFn extends (...args: any[]) => Promise<any>,
>(
  action: TFn,
  args: Parameters<TFn>,
  workStore: WorkStore,
  requestStore: RequestStore,
  shouldSkipPageRendering: boolean
): Promise<{
  actionResult: Awaited<ReturnType<TFn>>
  skipPageRendering: boolean
}> {
  requestStore.phase = 'action'
  let skipPageRendering = shouldSkipPageRendering
 
  if (args.length > SERVER_ACTION_ARGS_LIMIT) {
    throw new Error(
      `Server Action arguments list is too long (${args.length}). Maximum allowed is ${SERVER_ACTION_ARGS_LIMIT}.`
    )
  }

Sources: packages/next/src/server/app-render/action-handler.ts:1456-1480

Constant NameValuePurpose
SERVER_ACTION_ARGS_LIMIT1000Limits the maximum number of arguments passed to a server action to prevent stack overflows during .apply() invocation.

Sources: packages/next/src/server/app-render/action-handler.ts:1459-1480

Caution

If the length of the arguments array passed to a Server Action exceeds SERVER_ACTION_ARGS_LIMIT (1,000 items), execution aborts immediately by throwing a descriptive Error, halting malformed payloads before action.apply() is ever evaluated packages/next/src/server/app-render/action-handler.ts:1476-1480.

The action function executes within the workUnitAsyncStorage store context packages/next/src/server/app-render/action-handler.ts:1483-1485. Whether the subsequent page rendering pass can be skipped depends on revalidation outcomes packages/next/src/server/app-render/action-handler.ts:1487-1492. If the path was not revalidated or the result indicates ActionDidNotRevalidate, skipPageRendering is evaluated as true packages/next/src/server/app-render/action-handler.ts:1489-1492.

 
try {
  const actionResult = await workUnitAsyncStorage.run(requestStore, () =>
    action.apply(null, args)
  )
 
  // If the page was not revalidated, or if this is an action-only request,
  // we can skip rendering the page.
  skipPageRendering ||=
    workStore.pathWasRevalidated === undefined ||
    workStore.pathWasRevalidated === ActionDidNotRevalidate
 
  return { actionResult, skipPageRendering }

Sources: packages/next/src/server/app-render/action-handler.ts:1481-1493

When page rendering is required, the finally block of executeActionAndPrepareForRender manages post-action synchronization packages/next/src/server/app-render/action-handler.ts:1494-1514. The request store phase switches from 'action' to 'render' packages/next/src/server/app-render/action-handler.ts:1496.

} finally {
  if (!skipPageRendering) {
    requestStore.phase = 'render'
 
    // When we switch to the render phase, cookies() will return
    // `workUnitStore.cookies` instead of
    // `workUnitStore.userspaceMutableCookies`. We want the render to see any
    // cookie writes that we performed during the action, so we need to update
    // the immutable cookies to reflect the changes.
    synchronizeMutableCookies(requestStore)
 
    // The server action might have toggled draft mode, so we need to reflect
    // that in the work store to be up-to-date for subsequent rendering.
    workStore.isDraftMode = requestStore.draftMode.isEnabled
 
    // If the action called revalidateTag/revalidatePath, then that might
    // affect data used by the subsequent render, so we need to make sure all
    // revalidations are applied before that.
    await executeRevalidates(workStore)
  }
}

Sources: packages/next/src/server/app-render/action-handler.ts:1494-1514

Note

During the action phase, cookies() accesses workUnitStore.userspaceMutableCookies. Upon transitioning to the render phase, synchronizeMutableCookies updates immutable cookies so subsequent rendering correctly observes all cookie mutations performed during action execution packages/next/src/server/app-render/action-handler.ts:1498-1504.

Additionally, the work store's draft mode status is updated from requestStore.draftMode.isEnabled to handle toggles during action execution, and any pending revalidations invoked via revalidateTag or revalidatePath are applied via executeRevalidates before rendering begins packages/next/src/server/app-render/action-handler.ts:1505-1513. Finally, generateFlight constructs the resulting stream payload utilizing temporary references and revalidation waitUntil promises packages/next/src/server/app-render/action-handler.ts:1437-1447.

return {
  type: 'done',
  result: await generateFlight(ctx, requestStore, {
    actionResult: promise,
    // If the page was not revalidated, or if this is an action-only
    // request, we can skip rendering the page.
    skipPageRendering,
    temporaryReferences,
    waitUntil: getRevalidationWaitUntil(workStore, skipPageRendering),
  }),
}

Sources: packages/next/src/server/app-render/action-handler.ts:1437-1447

Client state transition and cache invalidation

Reducer processing and cache invalidation

The client-side handling of server action responses begins inside serverActionReducer, which evaluates the action result and inspects the returned revalidationKind packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:317-349. If the revalidation kind is anything other than ActionDidNotRevalidate, the reducer invokes invalidateBfCache() to evict dynamic data stored in the back-forward cache, sets action.didRevalidate = true on the action object, and schedules cache invalidations packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:349-366.

To revalidate client route caches, invalidateEntirePrefetchCache is invoked, which coordinates the clearing of both route and segment prefetch data packages/next/src/client/components/segment-cache/cache.ts:482-491.

  1. serverActionReducer processes the server action response and checks if revalidationKind !== ActionDidNotRevalidate packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:349-349.
  2. invalidateEntirePrefetchCache increments currentRouteCacheVersion and currentSegmentCacheVersion, then invokes pingVisibleLinks and pingInvalidationListeners packages/next/src/client/components/segment-cache/cache.ts:482-491.
  3. pingVisibleLinks iterates over prefetchableAndVisible link instances, checks if any task is dirty, cancels existing stale prefetch tasks via cancelPrefetchTask, and constructs fresh cache keys packages/next/src/client/components/links.ts:379-400.
  4. createCacheKey parses the target link href into a RouteCacheKey object containing a normalized pathname, search string, and nextUrl packages/next/src/client/components/segment-cache/cache-key.ts:20-31.
export function serverActionReducer(
  state: ReadonlyReducerState,
  action: ServerActionAction
): ReducerState {
  const { resolve, reject } = action
 
  // only pass along the `nextUrl` param (used for interception routes) if the current route was intercepted.
  // If the route has been intercepted, the action should be as well.
  // Otherwise the server action might be intercepted with the wrong action id
  // (ie, one that corresponds with the intercepted route)
  const nextUrl =
    // We always send the last next-url, not the current when
    // performing a dynamic request. This is because we update
    // the next-url after a navigation, but we want the same
    // interception route to be matched that used the last
    // next-url.
    (state.previousNextUrl || state.nextUrl) &&
    hasInterceptionRouteInCurrentTree(state.tree)
      ? state.previousNextUrl || state.nextUrl
      : null
 
  return fetchServerAction(state, nextUrl, action).then(
    async ({
      revalidationKind,
      actionResult,
      actionFlightData: flightData,
      actionFlightDataRenderedSearch: flightDataRenderedSearch,
      redirectLocation,
      redirectType,
      isPrerender,
      couldBeIntercepted,
    }) => {
      if (revalidationKind !== ActionDidNotRevalidate) {
        // There was either a revalidation or a refresh, or maybe both.
 
        // Evict the BFCache, which may contain dynamic data.
        invalidateBfCache()
 
        // Store whether this action triggered any revalidation
        // The action queue will use this information to potentially
        // trigger a refresh action if the action was discarded
        // (ie, due to a navigation, before the action completed)
        action.didRevalidate = true
 
        // If there was a revalidation, evict the prefetch cache.
        // TODO: Evict only segments with matching tags and/or paths.
        // TODO: We should only invalidate the route cache if cookies were
        // mutated, since route trees may vary based on cookies. For now we
        // invalidate both caches until we have a way to detect cookie
        // mutations on the client.

Sources: packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:317-366

Note

Invalidation does not eagerly delete cache entries from memory; instead, version counters (currentRouteCacheVersion and currentSegmentCacheVersion) are incremented, causing older entries to be lazily evicted when subsequently accessed packages/next/src/client/components/segment-cache/cache.ts:463-491.

The action revalidation behavior is driven by specific integer or string flags representing different levels of cache invalidation boundaries returned by the server.

Revalidation Kind ConstantMeaning
ActionDidNotRevalidateThe server action did not perform any revalidations or cache changes.
ActionDidRevalidateDynamicOnlyThe server action revalidated dynamic data paths or tags only.
ActionDidRevalidateStaticAndDynamicThe server action revalidated both static pages and dynamic data paths.

Sources: packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:64-68

Warning

Because route trees may vary based on cookies, client-side server actions currently invalidate both route and segment caches indiscriminately until precise cookie mutation tracking is available on the client packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:362-366.

Was this page helpful?