---
title: "Server Actions"
description: "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, a..."
last_updated: "2026-10-12T06:38:56.887958+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/server-actions"
---

<details>
<summary>Relevant source files</summary>

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

- [packages/next/src/server/app-render/action-handler.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts)
- [packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts)
- [packages/next/src/server/route-modules/app-route/module.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-route/module.ts)
- [packages/next/src/client/components/app-router-instance.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts)
- [packages/next/src/server/app-render/encryption.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/encryption.ts)
- [packages/next/src/server/dev/hot-reloader-turbopack.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/dev/hot-reloader-turbopack.ts)
- [packages/next/src/build/templates/app-page-runtime.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts)
- [packages/next/src/build/templates/app-route.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts)
- [packages/next/src/server/base-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/base-server.ts)
- [packages/next/src/server/lib/router-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-server.ts)
- [packages/next/src/server/next-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/next-server.ts)
- [packages/next/src/client/sequential-call-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/sequential-call-server.ts)
- [packages/next/src/client/app-call-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/app-call-server.ts)
- [packages/next/src/server/dev/dev-validation-worker.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/dev/dev-validation-worker.ts)
- [packages/next/src/client/concurrent-call-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/concurrent-call-server.ts)
- [packages/next/src/client/components/app-router.tsx](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router.tsx)
- [packages/next/src/server/app-render/manifests-singleton.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/manifests-singleton.ts)
- [packages/next/src/client/components/router-reducer/router-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer.ts)
- [packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts)
- [packages/next/src/server/lib/server-action-request-meta.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/server-action-request-meta.ts)
- [packages/next/src/client/components/use-action-queue.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/use-action-queue.ts)
- [packages/next/src/server/app-render/react-server.node.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/react-server.node.ts)
- [packages/next/src/next-devtools/dev-overlay.browser.tsx](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/dev-overlay.browser.tsx)
- [packages/next/src/server/app-render/csrf-protection.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/csrf-protection.ts)
- [packages/next/src/client/components/links.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/links.ts)
- [packages/next/src/client/components/segment-cache/cache-key.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/cache-key.ts)
- [packages/next/src/client/components/segment-cache/cache.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/cache.ts)
</details>

## 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L1110-L1162). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1473-L1515). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L317-L366).

## 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L1110-L1113). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L1116-L1122).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L1114-L1136)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L1139-L1190).

```typescript
  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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L1163-L1190)

### 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]]. 

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/manifests-singleton.ts#L16-L23)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/manifests-singleton.ts#L31-L54).

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/manifests-singleton.ts#L25-L29).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/manifests-singleton.ts#L25-L29)

## 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]].

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/sequential-call-server.ts#L17-L28)

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts#L152-L184). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts#L186-L196). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts#L197-L210).

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts#L112-L128).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts#L112-L128)

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts#L30-L150).

| Type / Function | Purpose |
| :--- | :--- |
| `AppRouterActionQueue` | Holds the canonical state, dispatch function, action reducer, and pointers for `pending`, `last`, and flags (`needsRefresh`, `wasPreempted`). |
| `ActionQueueNode` | Represents an item in the queue containing `payload`, `next` pointer, deferred `resolve`/`reject` callbacks, and a `discarded` boolean flag. |
| `runAction` | Executes the current action payload against `prevState`, wrapping promise resolution or triggering `handleError` and `handleResult`. |
| `runRemainingActions` | Advances `actionQueue.pending` to `settledAction.next`, transitions final states when preemption clears, or flushes pending refreshes. |
| `dispatchAction` | Creates 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts#L30-L210)

## 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/server-action-request-meta.ts#L6-L14). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/server-action-request-meta.ts#L16-L44).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/server-action-request-meta.ts#L6-L52)

| Metadata Property | Evaluation Rule | Purpose |
| :--- | :--- | :--- |
| `actionId` | `req.headers.get(ACTION_HEADER) ?? null` | Extracts the target action identifier header (`next-action`). |
| `isURLEncodedAction` | `req.method === 'POST' && contentType === 'application/x-www-form-urlencoded'` | Identifies URL-encoded form posts (rejected by the action handler). |
| `isMultipartAction` | `req.method === 'POST' && contentType?.startsWith('multipart/form-data')` | Identifies multipart form action submissions. |
| `isFetchAction` | `actionId !== undefined && typeof actionId === 'string' && req.method === 'POST'` | Identifies standard fetch-based Server Action calls. |
| `isPossibleServerAction` | `isFetchAction \|\| isURLEncodedAction \|\| isMultipartAction` | Master boolean indicating any candidate Server Action request. |

Sources: [packages/next/src/server/lib/server-action-request-meta.ts:15-51](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/server-action-request-meta.ts#L15-L51)

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/csrf-protection.ts#L72-L94). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/csrf-protection.ts#L6-L94).

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/csrf-protection.ts#L25-L32).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/csrf-protection.ts#L72-L94)

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1558-L1606). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1579-L1585).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1558-L1606)

## 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1522-L1547). If the action ID header is missing, an invariant error is raised [packages/next/src/server/app-render/action-handler.ts:1527-1529](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1527-L1529). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1533-L1544). 

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1522-L1547)

Execution proceeds inside `executeActionAndPrepareForRender`, which transitions the request store phase to `'action'` [packages/next/src/server/app-render/action-handler.ts:1473](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1473). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1456-L1480).

```typescript
 * 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1456-L1480)

| Constant Name | Value | Purpose |
| :--- | :--- | :--- |
| `SERVER_ACTION_ARGS_LIMIT` | `1000` | Limits 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1459-L1480)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1476-L1480).

### Execution, cookie synchronization, and Flight preparation

The action function executes within the `workUnitAsyncStorage` store context [packages/next/src/server/app-render/action-handler.ts:1483-1485](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1483-L1485). Whether the subsequent page rendering pass can be skipped depends on revalidation outcomes [packages/next/src/server/app-render/action-handler.ts:1487-1492](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1487-L1492). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1489-L1492).

```typescript

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1481-L1493)

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1494-L1514). The request store phase switches from `'action'` to `'render'` [packages/next/src/server/app-render/action-handler.ts:1496](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1496).

```typescript
} 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1494-L1514)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1498-L1504).

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1505-L1513). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1437-L1447).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.ts#L1437-L1447)

## 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L317-L349). 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L349-L366).

```mermaid
sequenceDiagram
    participant serverActionReducer as serverActionReducer
    participant invalidateEntirePrefetchCache as invalidateEntirePrefetchCache
    participant pingVisibleLinks as pingVisibleLinks
    participant createCacheKey as createCacheKey

    serverActionReducer->>invalidateEntirePrefetchCache: invalidateEntirePrefetchCache(nextUrl, root)
    invalidateEntirePrefetchCache->>pingVisibleLinks: pingVisibleLinks(nextUrl, root)
    pingVisibleLinks->>createCacheKey: createCacheKey(instance.prefetchHref, nextUrl)
```

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/cache.ts#L482-L491). 

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L349-L349).
2. `invalidateEntirePrefetchCache` increments `currentRouteCacheVersion` and `currentSegmentCacheVersion`, then invokes `pingVisibleLinks` and `pingInvalidationListeners` [packages/next/src/client/components/segment-cache/cache.ts:482-491](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/cache.ts#L482-L491).
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/links.ts#L379-L400).
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/cache-key.ts#L20-L31).

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L317-L366)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/cache.ts#L463-L491).

### Revalidation kinds and link re-pinging

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 Constant | Meaning |
| :--- | :--- |
| `ActionDidNotRevalidate` | The server action did not perform any revalidations or cache changes. |
| `ActionDidRevalidateDynamicOnly` | The server action revalidated dynamic data paths or tags only. |
| `ActionDidRevalidateStaticAndDynamic` | The 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L64-L68)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L362-L366).

## Related

- [RSC Transport](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/rsc-transport)
- [Router Reducer](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/router-reducer)
- [App Rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/app-rendering)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
