cf.completefrontend
Open lab My progress
THE NEXT.JS FIELD GUIDE

'use client' and 'use server' under the hood

See what Next.js 16 compiles 'use client' and 'use server' files into: client references, the manifests, action ids, and the packages that carry them.

By the end, you can
  • 01
    Read the compiled referencesFind what the build made of a 'use client' file and a 'use server' file: a proxy that registers client references, and functions registered with an id.
  • 02
    Follow a reference through the manifestsTrace a client reference from its id to the chunks the browser loads, and an action id from the browser's request to the function the server runs.
  • 03
    Explain the boundarySay which React package carries references under Turbopack and webpack, and why a function prop stops the build unless it's a Server Function.

One button, three copies

You put "use client" at the top of a file, and its component runs in the browser. You put "use server" at the top of another, and the browser can call its functions. Neither line does anything by itself: both are just strings. Next.js's compiler reads them while it builds the app and writes different code for each side, and you can read that code in the build.

The app is the tea shop's Mint tea page. The page is a Server Component, and it renders one Client Component, an Add to cart button. The button calls one Server Function, addToCart. This is a real Next.js 16.4.3 project, built with next build and run with next start. Click the files to read them:

The Mint tea page, with one Client Component and one Server FunctionNext.js
app/
app/add-to-cart.tsx
"use client"; import { useState } from "react";import { addToCart } from "./actions"; export default function AddToCart({ tea }: { tea: string }) {  const [added, setAdded] = useState(false);  async function add() {    await addToCart(tea);    setAdded(true);  }  return (    <button onClick={add} disabled={added}>      {added ? "Added to cart" : "Add to cart"}    </button>  );} export function CartBadge() {  return <span>Your cart</span>;}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
Mint tea

Cool and fresh, $3 a tin.

Add to cart

The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.

app/add-to-cart.tsx, shown first, starts with "use client" on line 1. That line marks a boundary: AddToCart, and the ordinary modules its file imports, like react on line 3, also go to the browser. Line 4 is different: it imports addToCart from app/actions.ts, whose line 1 is "use server". An import from a "use server" file doesn't bring the function's code along: the browser gets a stub with an id in its place, and the code stays on the server. Line 9 calls it when the button is clicked, and line 10 changes the label to Added to cart. Lines 19 to 21 export a second component, CartBadge, which no page uses. In next.config.ts, line 8 turns off Turbopack's minifier, so the built JavaScript keeps its line breaks and names. That's only to make the build readable here; an app leaves it on. Open the Steps tab: after the click, the button says Added to cart, and the Terminal tab shows what next start printed, addToCart ran on the server for mint.

A few words first. A module is one file of JavaScript, with what it imports and exports. A bundler (Turbopack by default in Next.js 16, or webpack) follows the imports from each page and writes the modules into a few files called chunks. The compiler and bundler also give every module a number, its module id. And Next.js builds each module for one of several layers, one for each place the code runs: the layer where Server Components render (called RSC, for React Server Components), the layer that makes the first HTML on the server (SSR, for server-side rendering), and the browser. SWC and Turbopack showed how Turbopack keeps the layers in one module graph.

Two more. When Server Components render, React writes what they rendered as text, the RSC payload: a list of numbered lines called rows, which the browser's React reads. Flight is React's name for that format and for the code that writes and reads it. Client Components hydrate from it in the browser; Server Components never do, because their code never gets there.

The recording kept the files the build wrote for app/add-to-cart.tsx. There are three: one for each layer.

One file, app/add-to-cart.tsx, with three arrows to three copies. The RSC copy holds only registerClientReference calls. The SSR copy holds the real AddToCart and makes the first HTML. The browser copy holds the real AddToCart and runs the clicks.add-to-cart.tsxRSC copyregisterClientReference(…)SSR copythe real AddToCart: HTMLbrowserthe real AddToCart: clicks
What the build made of app/add-to-cart.tsx: three copies of one file, one for each place it's needed. Only the RSC copy, the one Server Components import, has no component in it.
The three copies of app/add-to-cart.tsx
CopyWhere it runsWhat it holds
The RSC copy (Server Components)On the server, while Server Components render the RSC payload.No component: each export is a client reference, a function that throws if anyone calls it.
The SSR copyOn the server, while React turns the payload into the first HTML.The real AddToCart, with useState, so its button is in the HTML.
The browser copyIn the browser, after the chunk loads.The same real code. React hydrates the button with it, and it runs the clicks.
Real-life analogyA coat check

At a theater, you hand your coat in at the door and get a numbered ticket. All evening you carry the ticket, not the coat: it's light, and it's enough to say which coat is yours. At the end, someone reads the list of hooks, finds your number, and hands you the coat.

In real life: Your coat, which stays in the cloakroom
In Next.js: The Client Component's code, which never runs in the RSC layer
In real life: The numbered ticket you carry around instead
In Next.js: A client reference: the file's path and the export's name
In real life: The cloakroom's list of which hook holds which ticket
In Next.js: The client reference manifest
In real life: Handing the ticket over at the door to get the coat back
In Next.js: The browser loading the chunk and the module the payload names
The main idea

The compiler turns each directive into a reference. A "use client" file becomes a client reference on the server, an id that says where its code is; a "use server" function becomes a server reference in the browser, an id the server can run. Two manifests, written during the build, turn each id back into code: the client reference manifest for the browser's modules, and the server reference manifest for the Server Functions.

Client Components and Server Actions taught these directives from the outside. Server Components inside: the Flight protocol, in the React course, read React's side with a small bundler of its own. Next.js's side is the compiler, the manifests and the server code around React.

What a Server Component gets instead of AddToCart

Line 1 of app/page.tsx imports AddToCart. The page renders in the RSC layer, so the bundler gives it the RSC copy of app/add-to-cart.tsx. That copy is a proxy: a stand-in module with the same exports as your file, but none of its code. This is that copy, one module of the chunk the recording kept, exactly as Turbopack wrote it:

The RSC copy of app/add-to-cart.tsx, from .next/server/chunks/ssr/app_15g8bbrs8b4on._.jsJavaScript
17843, ((__turbopack_context__) => {
"use strict";

// This file is generated by next-core EcmascriptClientReferenceModule.
var __TURBOPACK__imported__module__11857__ = __turbopack_context__.i(11857);
;
const CartBadge = (0, __TURBOPACK__imported__module__11857__["registerClientReference"])(function() {
    throw new Error("Attempted to call CartBadge() from the server but CartBadge is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");
}, "[project]/app/add-to-cart.tsx", "CartBadge");
const __TURBOPACK__default__export__ = (0, __TURBOPACK__imported__module__11857__["registerClientReference"])(function() {
    throw new Error("Attempted to call the default export of [project]/app/add-to-cart.tsx from the server, but it's on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");
}, "[project]/app/add-to-cart.tsx", "default");
__turbopack_context__.s([
    "default",
    0,
    __TURBOPACK__default__export__
]);
}),

The component is gone. In its place, line by line:

  1. Line 1 starts module 17843. A Turbopack chunk is a list of modules, each a number and a function that runs the module.
  2. Line 4 says who wrote this code: not you, but EcmascriptClientReferenceModule, part of Next.js's Rust code.
  3. Line 5 imports module 11857, which is React's Flight server for Turbopack (more on it below).
  4. Lines 7 to 9 make CartBadge: a function that only throws an error, passed to registerClientReference with the file's path, [project]/app/add-to-cart.tsx, and the export's name.
  5. Lines 10 to 12 do the same for the default export, "default".
  6. Lines 13 to 17 export only default. Nothing uses CartBadge, so the bundler dropped it from the exports, but its registerClientReference call stayed.

So when MintPage renders <AddToCart tea="mint" />, the element's type is that throwing function. If a Server Component called it as a function, AddToCart(), the error on line 11 would say why that can't work. React never calls it: it recognizes the function as a client reference and writes a pointer to the code instead of rendering it.

Where does the proxy come from? Turbopack writes it as text, in ecmascript_client_reference_module.rs, lines 79 to 150. For each export of the client file, it writes this template, from lines 117 to 125:

The template for each export: ecmascript_client_reference_module.rs, lines 117 to 125Rust
writedoc!(
    code,
    r#"
        export const {export_name} = registerClientReference(
            function() {{ throw new Error({call_err}); }},
            {server_module_path},
            {export_name_str},
        );
    "#,

writedoc! writes text into code, the new module. Lines 4 to 8 are the JavaScript you saw in lines 7 to 9 of the RSC copy, with the blanks in braces filled in: the export's name, the error message, the file's path and the name as a string. (In this Rust text, {{ stands for a single {.) A loop around it, from line 97, runs it once for every export the client file has; the default export gets a template of its own, from line 98. This is the client reference transform: not a change to your file, but a second module that the RSC layer imports in its place.

webpack builds (next build --webpack) do the same in TypeScript: next-flight-loader/index.ts, lines 123 to 147, write an import of registerClientReference from react-server-dom-webpack/server and one call for each export, with the same error messages.

registerClientReference is React's. It turns the throwing function into a reference, in ReactFlightTurbopackReferences.js, lines 37 to 59 of React 19.3.1, used here as the reference source. The runtime Next.js 16.4.3 ships is its own build, React 19.3.0-canary-278794d7-20261002, in next/dist/compiled; these functions match there:

registerClientReference: ReactFlightTurbopackReferences.js, lines 37 to 59JavaScript
export function registerClientReference<T>(
  proxyImplementation: any,
  id: string,
  exportName: string,
): ClientReference<T> {
  return registerClientReferenceImpl(
    proxyImplementation,
    id + '#' + exportName,
    false,
  );
}

function registerClientReferenceImpl<T>(
  proxyImplementation: any,
  id: string,
  async: boolean,
): ClientReference<T> {
  return Object.defineProperties(proxyImplementation, {
    $$typeof: {value: CLIENT_REFERENCE_TAG},
    $$id: {value: id},
    $$async: {value: async},
  });
}

Line 8 joins the path and the name with #, so the default export's id is [project]/app/add-to-cart.tsx#default. Lines 18 to 22 add three properties to the function with Object.defineProperties: $$typeof, a symbol that marks it as a client reference; $$id, the id; and $$async, for modules that load asynchronously. When React meets this function while it serializes the tree, it writes an import row into the RSC payload instead: one numbered row that names a module, its chunks and an export. The React lesson follows that step in serializeClientReference.

The other two copies are your code, compiled. Pick each one below. The SSR copy and the browser copy are the same component; only the module numbers differ, and line 4 names the layer each was built for: [app-ssr] or [app-client].

Playground: one file, three copies
From .next/server/chunks/ssr/app_15g8bbrs8b4on._.jsJavaScript
17843, ((__turbopack_context__) => {
"use strict";

// This file is generated by next-core EcmascriptClientReferenceModule.
var __TURBOPACK__imported__module__11857__ = __turbopack_context__.i(11857);
;
const CartBadge = (0, __TURBOPACK__imported__module__11857__["registerClientReference"])(function() {
    throw new Error("Attempted to call CartBadge() from the server but CartBadge is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");
}, "[project]/app/add-to-cart.tsx", "CartBadge");
const __TURBOPACK__default__export__ = (0, __TURBOPACK__imported__module__11857__["registerClientReference"])(function() {
    throw new Error("Attempted to call the default export of [project]/app/add-to-cart.tsx from the server, but it's on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");
}, "[project]/app/add-to-cart.tsx", "default");
__turbopack_context__.s([
    "default",
    0,
    __TURBOPACK__default__export__
]);
}),
Try it yourself
Server Components (RSC)

The copy Server Components import: no component at all. Each export is a function that throws if called, registered as a client reference with the file's path and the export's name.

Each copy is one module of a chunk from the recorded build of the Mint tea page, built by Next.js 16.4.3 with Turbopack's minifier off. Chunk names and module ids are this build's. Reset goes back to the RSC copy.

Lines 8 to 13 of the SSR and browser copies belong to the Server Function, and come back later. The rest is AddToCart, with the JSX turned into jsx("button", …) calls. The server runs the SSR copy so the button is in the first HTML, and the browser runs its copy to make the same button work: hydration.

createClientModuleProxy: one reference for a whole module

The loop over exports needs to know the exports. In an ES module, they're written in the code (export default, export function), so the compiler can list them without running anything. A CommonJS module, the format Node.js used before ES modules, sets module.exports while it runs (Node.js module loading compares the two). Its exports can't be listed ahead of time. Line 81 of the Rust file checks which kind it is: if let EcmascriptExports::EsmExports(exports). The other branch is lines 138 to 147:

The proxy for a module that isn't an ES module: ecmascript_client_reference_module.rs, lines 138 to 147Rust
} else {
    is_esm = false;
    writedoc!(
        code,
        r#"
            // This file is generated by next-core EcmascriptClientReferenceModule.
            const {{ createClientModuleProxy }} = require("react-server-dom-turbopack/server");

            {TURBOPACK_EXPORT_NAMESPACE}(createClientModuleProxy({server_module_path}));
        "#,

Line 2 notes that it isn't an ES module. Line 7 takes createClientModuleProxy from the same Flight server, and line 9 exports one proxy for the whole module, made from its path alone. Your app has no CommonJS client file, but Next.js has one: its built-in global-error.js, the page it shows when the root layout itself fails. The recorded build has its proxy:

The RSC copy of Next.js's global-error.js, from .next/server/chunks/ssr/[root-of-the-server]__0py1t7q9ntzgc._.jsJavaScript
82509, ((__turbopack_context__, module, exports) => {

// This file is generated by next-core EcmascriptClientReferenceModule.
const { createClientModuleProxy } = __turbopack_context__.r(11857);
__turbopack_context__.n(createClientModuleProxy("[project]/node_modules/next/dist/client/components/builtin/global-error.js"));
}),

Line 3 is the same signature comment. Line 4 takes createClientModuleProxy from module 11857, and line 5 makes one reference for [project]/node_modules/next/dist/client/components/builtin/global-error.js. What does that reference do when a Server Component reads .default from it? React's createClientModuleProxy, lines 349 to 362:

createClientModuleProxy: ReactFlightTurbopackReferences.js, lines 349 to 362JavaScript
export function createClientModuleProxy<T>(
  moduleId: string,
): ClientReference<T> {
  const clientReference: ClientReference<T> = registerClientReferenceImpl(
    {} as any,
    // Represents the whole Module object instead of a particular import.
    moduleId,
    false,
  );
  // $FlowFixMe[incompatible-variance]
  // $FlowFixMe[incompatible-type]
  // $FlowFixMe[incompatible-exact]
  return new Proxy(clientReference, proxyHandlers);
}

Lines 4 to 8 make a client reference whose id is the module's path, with no #: as the comment on line 6 says, it stands for the whole module. Line 13 wraps it in a Proxy, an object that runs your code whenever a property is read. Its get handler, lines 290 to 312, makes a new client reference for any name you read, with the id path#name, the same id registerClientReference would have made. So both kinds of proxy end in the same references; one lists the names ahead of time, and the other makes them when they're read.

webpack builds use the same function. For a CommonJS client file, its loader writes module.exports = createProxy(path), and createProxy is createClientModuleProxy under another name:

next-flight-loader/module-proxy.ts, lines 1 to 5TypeScript
/* eslint-disable import/no-extraneous-dependencies */
import { createClientModuleProxy } from 'react-server-dom-webpack/server'

// Re-assign to make it typed.
export const createProxy: (moduleId: string) => any = createClientModuleProxy

Line 2 imports it from react-server-dom-webpack/server, and line 5 renames it. (module-proxy.ts, and its caller in index.ts, lines 174 to 181.)

The client reference manifest

A client reference holds a path and a name. The browser needs something else: which chunk files to download, and which module to run in them. The bundler knows both, because it wrote the chunks. So during the build it writes one more file for each page, the client reference manifest: a list that turns a client file's path into its modules and chunks. For the Mint tea page it's .next/server/app/page_client-reference-manifest.js, 4933 bytes. It's a script that sets globalThis.__RSC_MANIFEST["/page"] to one long line of JSON. Most of it is about Next.js's own Client Components, like the router; these are the parts about AddToCart, laid out to read:

The parts of .next/server/app/page_client-reference-manifest.js about app/add-to-cart.tsxJSON
{
  "clientModules": {
    "[project]/app/add-to-cart.tsx": {
      "id": 7870,
      "name": "*",
      "chunks": [
        "/_next/static/chunks/30e5tuscv0mzh.js",
        "/_next/static/chunks/0cvbcc51ycerw.js"
      ],
      "async": false
    }
  },
  "ssrModuleMapping": {
    "7870": {
      "*": {
        "id": 52554,
        "name": "*",
        "chunks": [
          "server/chunks/ssr/[root-of-the-server]__01x_r3zt5j4wb._.js",
          "server/chunks/ssr/node_modules_next_dist_04kmhk4unimjf._.js",
          "server/chunks/ssr/_18r24brtkxts6._.js"
        ],
        "async": false
      }
    }
  },
  "rscModuleMapping": {
    "7870": {
      "*": {
        "id": 17843,
        "name": "*",
        "chunks": [],
        "async": false
      }
    }
  },
  "entryJSFiles": {
    "[project]/app/page": [
      "static/chunks/30e5tuscv0mzh.js",
      "static/chunks/0cvbcc51ycerw.js"
    ]
  }
}
  1. Lines 3 to 11, in clientModules: the path from the reference's $$id gives the browser's module, 7870, and two chunks to load first (lines 6 to 9). "name": "*" on line 5 means the entry stands for the whole module: React takes the export's name from after the # in the id.
  2. Lines 13 to 26, in ssrModuleMapping: the browser's module 7870 becomes the SSR copy, module 52554, in three server chunks. That's how the server makes HTML from a payload that names the browser's module.
  3. Lines 27 to 36, in rscModuleMapping: the same module back to the RSC copy, 17843, the proxy you read above.
  4. Lines 37 to 42, in entryJSFiles: the chunks the page's Client Components need, which the HTML loads with <script> tags.

Inside next build lists the build's other manifests and what reads each one.

What each part of the manifest maps, and what reads it
FieldWhat it mapsWho reads it
clientModulesA client file's path to the browser module's id and the chunks to load for it.React's Flight server, which writes the I row for a client reference.
ssrModuleMappingThe browser module's id to the SSR copy's id and its server chunks.React's Flight client on the server, while it makes the HTML.
rscModuleMappingThe browser module's id to the RSC copy's id.Code that reads Flight data back on the server, like an action's encrypted closure values or a "use cache" entry.
entryJSFilesEach layout and page to the browser chunks its Client Components need.The HTML render, which adds a <script> tag for each.
moduleLoadingThe prefix put before every chunk's address.Both Flight clients, when they load a chunk.

Turbopack writes the file in client_reference_manifest.rs, lines 396 to 453, one whole-module entry per client file, and the script around the JSON at lines 536 and 537. (webpack's is ClientReferenceManifestPlugin, in flight-manifest-plugin.ts.) When next start loads the page, it runs that script and keeps the result, in load-components.ts, lines 151 to 173:

loadClientReferenceManifestForPage: load-components.ts, lines 151 to 173TypeScript
export async function loadClientReferenceManifestForPage(
  distDir: string,
  page: string,
  attempts?: number
): Promise<DeepReadonly<ClientReferenceManifest> | undefined> {
  const entryName = page.replace(/%5F/g, '_')

  const manifestPath = join(
    /* turbopackIgnore: true */ distDir,
    'server',
    'app',
    entryName + '_' + CLIENT_REFERENCE_MANIFEST + '.js'
  )

  try {
    const context = await evalManifestWithRetries<{
      __RSC_MANIFEST: { [key: string]: ClientReferenceManifest }
    }>(manifestPath, attempts)
    return context.__RSC_MANIFEST[entryName]
  } catch (err) {
    return undefined
  }
}

Lines 8 to 13 build the file's path from the page's name: server/app/page_client-reference-manifest.js for /page. Lines 16 to 18 run it, and line 19 returns the entry it put in __RSC_MANIFEST. Next.js then stores it where every render can reach it (lines 305 to 314).

Now follow AddToCart through one request. React's Flight server walks the tree, finds the reference, looks up its path in clientModules, and writes an import row. The recording asked for the page's RSC payload (with the rsc: 1 header, as the router does). These are its rows about AddToCart:

Three rows of the recorded RSC payload of /Text
2:"/_next/static/chunks/30e5tuscv0mzh.js"
7:"/_next/static/chunks/0cvbcc51ycerw.js"
8:I[7870,["$2","$7"],"default"]

Line 3 is the import row: I, the module id 7870, the two chunks, and the export's name, "default". The chunks are written as "$2" and "$7", pointers to lines 1 and 2, so a long file name is written once. CartBadge has no row: only what renders gets one. The rest of the format is for The RSC payload, later in this stage.

Two readers use that row. To make the first HTML, the server reads the payload with React's Flight client and the ssrModuleMapping, in use-flight-response.tsx, lines 47 to 72:

getFlightStream, from use-flight-response.tsx, lines 47 to 72TypeScript
const { moduleLoading, edgeSSRModuleMapping, ssrModuleMapping } =
  getClientReferenceManifest()

let newResponse: Promise<T>
if (flightStream instanceof ReadableStream) {
  // The types of flightStream and debugStream should match.
  if (debugStream && !(debugStream instanceof ReadableStream)) {
    throw new InvariantError('Expected debug stream to be a ReadableStream')
  }

  // react-server-dom-webpack/client.edge must not be hoisted for require cache clearing to work correctly
  const { createFromReadableStream } =
    // eslint-disable-next-line import/no-extraneous-dependencies
    require('react-server-dom-webpack/client') as typeof import('react-server-dom-webpack/client')

  newResponse = createFromReadableStream<T>(flightStream, {
    findSourceMapURL,
    serverConsumerManifest: {
      moduleLoading,
      moduleMap: isEdgeRuntime ? edgeSSRModuleMapping : ssrModuleMapping,
      serverModuleMap: null,
    },
    nonce,
    debugChannel: debugStream ? { readable: debugStream } : undefined,
    endTime: debugEndTime,
  })

Lines 1 and 2 read the manifest. Line 16 starts reading the payload, and lines 18 to 22 give React the module map: on Node.js, line 20 picks ssrModuleMapping. So the row's 7870 loads module 52554, the SSR copy, and the button lands in the HTML. In the browser, the router reads the same row, loads the two chunks, and runs module 7870, the browser copy.

A client reference with the id add-to-cart.tsx#default goes into clientModules in the manifest, which gives the import row I[7870, chunks, "default"]. From that row, an arrow goes to the SSR copy, module 52554, found with ssrModuleMapping, and another to the browser, which loads the chunks and requires module 7870.…tsx#defaultthe reference's $$idclientModulesin the manifestI[7870, …]the payload's import rowSSR copyssrModuleMappingrequire(52554)browserload the chunksrequire(7870)
React on the server looks the reference up in clientModules and writes an import row. The server's HTML render and the browser both read that row: the server finds the SSR copy with ssrModuleMapping, and the browser loads the chunks. The ids are this build's.

"use server" becomes an id

Server Functions go the other way: the code stays on the server, and the browser gets a pointer. The compiler for this is SWC, the Rust compiler Next.js uses for every file, with a transform of Next.js's own, server_actions.rs. It runs on app/actions.ts once for each layer, and writes different code for each. This is the RSC layer's module, from the recorded build:

app/actions.ts in the RSC layer, from .next/server/chunks/ssr/_060_7v5by6pcr._.jsJavaScript
57526, ((__turbopack_context__) => {
"use strict";

// MERGED MODULE: [project]/.next-internal/server/app/page/actions.js { ACTIONS_MODULE0 => "[project]/app/actions.ts [app-rsc] (ecmascript)" } [app-rsc] (server actions loader, ecmascript)
;
// MERGED MODULE: [project]/.next-internal/server/app/page/actions.js { ACTIONS_MODULE0 => "[project]/app/actions.ts [app-rsc] (ecmascript)" } [app-rsc] (server actions loader, ecmascript) <locals>
;
// MERGED MODULE: [project]/app/actions.ts [app-rsc] (ecmascript)
;
var __TURBOPACK__imported__module__37936__ = __turbopack_context__.i(37936);
var __TURBOPACK__imported__module__13095__ = __turbopack_context__.i(13095);
;
async function addToCart(tea) {
    // A stand-in for saving the cart: this example only logs.
    console.log(`addToCart ran on the server for ${tea}`);
}
;
(0, __TURBOPACK__imported__module__13095__["ensureServerEntryExports"])([
    addToCart
]);
(0, __TURBOPACK__imported__module__37936__["registerServerReference"])(addToCart, "404c9eadfada699aa00315b9ad2b9fd234acedc2ca", null);
;
__turbopack_context__.s([
    "404c9eadfada699aa00315b9ad2b9fd234acedc2ca",
    0,
    addToCart
], 57526);
}),
  1. Lines 4 to 8 say which module this is: a server actions loader that Next.js made for the page, with your app/actions.ts merged into it.
  2. Lines 13 to 16 are your addToCart, unchanged. Its code stays here, on the server.
  3. Lines 18 to 20 check, while the app runs, that every export of a "use server" file is a function.
  4. Line 21 registers addToCart with React as a server reference, with its id, 404c9eadfada699aa00315b9ad2b9fd234acedc2ca. That's the action id.
  5. Lines 23 to 27 export the function under its id as the name. The server will look it up by that name.

The id is a hash the compiler makes from a secret, the file's path and the export's name, with a first byte about the function's arguments, built in generate_server_reference_id (server_actions.rs, lines 284 to 372). Securing Server Actions shows what the secret protects. Here, what matters is where it goes. The client side got a different module. Lines 8 to 13 of the browser copy, from the layers playground above:

Lines 8 to 13 of the browser copy of app/add-to-cart.tsx, from .next/static/chunks/0cvbcc51ycerw.jsJavaScript
// MERGED MODULE: [project]/app/data:78607a [app-client] (ecmascript) <text/javascript>
;
var __TURBOPACK__imported__module__95187__ = __turbopack_context__.i(95187);
/* __next_internal_action_entry_do_not_use__ [{"404c9eadfada699aa00315b9ad2b9fd234acedc2ca":{"name":"addToCart"}},"app/actions.ts",""] */ "use turbopack: no side effects";
;
const $$RSC_SERVER_ACTION_0 = /*#__PURE__*/ (0, __TURBOPACK__imported__module__95187__["createServerReference"])("404c9eadfada699aa00315b9ad2b9fd234acedc2ca", __TURBOPACK__imported__module__95187__["callServer"], void 0, __TURBOPACK__imported__module__95187__["findSourceMapURL"], "addToCart");

Line 1 names a module that doesn't exist on disk, app/data:…: the compiler made it from the import of ./actions. Line 4 is a comment the compiler leaves for the bundler, __next_internal_action_entry_do_not_use__, with each id, the function's name and its file: that's how the build finds every action to put in the manifest. Line 6 is the whole client side of addToCart: createServerReference with the id, a function called callServer, and the name. AddToCart calls that, not your function. The transform decides between the two outputs by the layer, in server_actions.rs, lines 2196 to 2210 and lines 2630 to 2632:

The two outputs, shortened from server_actions.rsRust
// If it's compiled in the client layer, each export field needs to be
// wrapped by a reference creation call.
let create_ref_ident = private_ident!("createServerReference");
let call_server_ident = private_ident!("callServer");
let find_source_map_url_ident = private_ident!("findSourceMapURL");

let client_layer_import = ((self.has_action || self.has_cache)
    && !self.config.is_react_server_layer)
    .then(|| {
        // import {
        //   createServerReference,
        //   callServer,
        //   findSourceMapURL
        // } from 'private-next-rsc-action-client-wrapper'
        // createServerReference("action_id")
// …
if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
    // Inlined actions are only allowed on the server layer.
    // import { registerServerReference } from 'private-next-rsc-server-reference'

Lines 7 and 8: in a file that has actions and isn't compiled for the RSC layer, the comment on lines 10 to 15 says what gets written, the import of createServerReference and one call for each export. Line 17 is the RSC layer's branch, which imports registerServerReference instead (line 19). The import comes from private-next-rsc-action-client-wrapper, an alias for action-client-wrapper.ts, which passes on React's createServerReference and Next.js's callServer. React's function is small, from ReactFlightReplyClient.js, lines 1489 to 1500 and 1527 to 1529:

createServerReference, shortened from ReactFlightReplyClient.jsJavaScript
export function createServerReference<A: Iterable<any>, T>(
  id: ServerReferenceId,
  callServer: CallServerCallback,
  encodeFormAction?: EncodeFormActionCallback,
  findSourceMapURL?: FindSourceMapURLCallback, // DEV-only
  functionName?: string,
): (...A) => Promise<T> {
  let action = function (): Promise<T> {
    // $FlowFixMe[method-unbinding]
    const args = Array.prototype.slice.call(arguments);
    return callServer(id, args);
  };
// …
  registerBoundServerReference(action, id, null, encodeFormAction);
  return action;
}

Lines 8 to 12 make the stub: a function that collects its arguments and hands them to callServer with the id. Line 14 marks it as a server reference too, so it can be passed on, to a form's action for example. Next.js's callServer sends the request through the router. The recording shows it: in the Steps tab of the first example, the click made one request, POST / with the header next-action: 404c9eadfada699aa00315b9ad2b9fd234acedc2ca, the same id.

The server reads that header and looks the id up in the other manifest, .next/server/server-reference-manifest.json. The recording kept it, with the build's secret key replaced by a placeholder:

.next/server/server-reference-manifest.json, as recordedJSON
{
  "node": {
    "404c9eadfada699aa00315b9ad2b9fd234acedc2ca": {
      "workers": {
        "app/page": {
          "moduleId": 57526,
          "async": false
        }
      },
      "filename": "app/actions.ts",
      "exportedName": "addToCart"
    }
  },
  "edge": {},
  "encryptionKey": "<encryptionKey>"
}

Line 3 is the id. Lines 4 to 9 say which module holds it for each page that can call it: for app/page, module 57526, the server actions loader you read above. Lines 10 and 11 name the file and the export, for error messages and logs. Line 15 held the key that encrypts values a Server Action closes over; the recorder never keeps it. The lookup is in manifests-singleton.ts, lines 344 to 412:

createServerModuleMap's lookup, shortened from manifests-singleton.tsTypeScript
if (!mightBeServerReferenceId(id)) {
  throw getInvalidServerReferenceIdError(id)
}

const workers =
  getServerActionsManifest()[
    process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node'
  ]?.[id]?.workers

if (!workers) {
  throw getActionNotFoundError(id)
}
// …
const { moduleId, async, durability } = workerEntry

return {
  id: moduleId,
  name: id,
  chunks: [],
  async,
  durability,

Lines 1 to 3 refuse anything that isn't shaped like an id. Lines 5 to 8 read workers from the manifest, and lines 10 to 12 answer "not found" for an id it doesn't know. Lines 14 to 21 return the module id and, as the name, the action id itself. Then action-handler.ts, lines 1251 to 1258, loads the module and picks the function by that name, the export from lines 23 to 27 of the RSC module:

action-handler.ts, lines 1251 to 1258TypeScript
const actionMod = (await ComponentMod.__next_app__.require(
  actionModId
)) as Record<string, (...args: unknown[]) => Promise<unknown>>
const actionHandler =
  actionMod[
    // `actionId` must exist if we got here, as otherwise we would have thrown an error above
    actionId!
  ]
In the browser, the stub made by createServerReference sends a POST to / with the header next-action: 404c9e…. On the server, server-reference-manifest.json gives module 57526 for that id, and addToCart runs there.browseraddToCart("mint")a stub fromcreateServerReferencePOST /next-actionservermanifest[id]gives the modulerequire(57526)addToCart runs hereid: 404c9e…all the browser knows
The browser never has addToCart's code. Its stub sends the id and the arguments; the server finds the module in server-reference-manifest.json and runs the real function. The id is this build's.

A Server Function can also reach the browser without any import there: as a prop. In this version, the page passes addToCart itself, and the Client Component calls addAction:

app/add-to-cart.tsx, getting the Server Function as a propTSX
"use client";

import { useState } from "react";

export default function AddToCart({ tea, addAction }: { tea: string; addAction: (tea: string) => Promise<void> }) {
  const [added, setAdded] = useState(false);
  async function add() {
    await addAction(tea);
    setAdded(true);
  }
  return (
    <button onClick={add} disabled={added}>
      {added ? "Added to cart" : "Add to cart"}
    </button>
  );
}

Nothing in this file imports actions.ts, so the browser's chunk has no createServerReference call. The page's RSC payload carries the reference instead. Its row for the prop holds the id:

The recorded payload row for addActionText
9:{"id":"40e7da8b795001f50144379efad899fcc777aa7956","bound":null}

React's Flight client turns that row into the same kind of stub when it reads the payload in the browser. In both versions the click sent a POST with a next-action header, and next start printed addToCart ran on the server for mint. Server Actions under the hood, later in this stage, follows that request into the server.

react-server-dom-webpack and react-server-dom-turbopack

Module 11857 keeps coming up. Every registerClientReference, createClientModuleProxy and registerServerReference above came from it. It's React's Flight server: Flight is the name of the format and the code that write and read the RSC payload. React publishes it in one package for each bundler, because each bundler loads chunks and modules its own way: react-server-dom-webpack, react-server-dom-turbopack, and others. The recorded build kept module 11857:

Module 11857, from .next/server/chunks/ssr/[root-of-the-server]__0py1t7q9ntzgc._.jsJavaScript
11857, ((__turbopack_context__, module, exports) => {
"use strict";

module.exports = __turbopack_context__.r(24951).vendored['react-rsc'].ReactServerDOMTurbopackServer;
}),

Line 4: it's ReactServerDOMTurbopackServer, from vendored['react-rsc'], the React that Next.js keeps for the RSC layer. The module it requires leads, in the same chunk, to this one:

Module 18622, from .next/server/chunks/ssr/[root-of-the-server]__0py1t7q9ntzgc._.jsJavaScript
18622, ((__turbopack_context__, module, exports) => {

var mod = __turbopack_context__.x("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js", () => require("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js"));

module.exports = mod;
}),

Line 3 loads next/dist/compiled/next-server/app-page-turbo.runtime.prod.js, the App Router's server runtime for Turbopack builds, with React and react-server-dom-turbopack bundled inside. Which one goes in is decided in vendored/rsc/entrypoints.ts, lines 35 to 75:

Which Flight server: shortened from vendored/rsc/entrypoints.tsTypeScript
if (process.env.TURBOPACK) {
  ReactServerDOMTurbopackServer =
    // @ts-expect-error -- TODO: Add types
    // eslint-disable-next-line import/no-extraneous-dependencies
    require('react-server-dom-turbopack/server') as typeof import('react-server-dom-turbopack/server')
// …
} else {
  ReactServerDOMWebpackServer =
    // eslint-disable-next-line import/no-extraneous-dependencies
    require('react-server-dom-webpack/server') as typeof import('react-server-dom-webpack/server')

Line 1: when the app was built by Turbopack, process.env.TURBOPACK is set, and lines 2 to 5 load react-server-dom-turbopack/server. Otherwise, line 7, lines 8 to 10 load the webpack one. The browser gets the matching Flight client. Next.js's own code imports react-server-dom-webpack/client by that name, as on line 14 of the getFlightStream excerpt. Under Turbopack, an alias swaps the name, in next_import_map.rs, lines 170 to 180:

The browser's alias: next_import_map.rs, lines 170 to 180Rust
import_map.insert_wildcard_alias(
    rcstr!("react-server-dom-webpack/"),
    request_to_import_mapping(app_dir.clone(), rcstr!("react-server-dom-turbopack/*")),
);
import_map.insert_wildcard_alias(
    rcstr!("react-server-dom-turbopack/"),
    request_to_import_mapping(
        app_dir.clone(),
        format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/*")
            .into(),
    ),

Lines 2 and 3 send every react-server-dom-webpack/… import to react-server-dom-turbopack/…, and lines 6 to 9 send that to Next.js's compiled copy. So a Turbopack build uses the Turbopack packages on both sides; a webpack build uses the webpack ones. Your app never imports either: they come with Next.js, in next/dist/compiled.

Turbopack and webpack builds, side by side
QuestionTurbopack (the default)webpack (next build --webpack)
Which bundler builds it?Turbopack, the default of next build and next dev in Next.js 16.webpack, with next build --webpack.
Who writes the client proxy?EcmascriptClientReferenceModule, in Rust.next-flight-loader, in TypeScript.
Which React package renders the payload?react-server-dom-turbopack/server.react-server-dom-webpack/server.
Which one reads it in the browser?react-server-dom-turbopack/client, aliased from the webpack name.react-server-dom-webpack/client.
Where are the files?In next/dist/compiled, bundled into app-page-turbo.runtime.prod.js.In next/dist/compiled, bundled into app-page.runtime.prod.js.
Internals, not an API

Module ids, chunk names, the manifests' shapes, the $$id format and the file names in .next are Next.js 16.4.3's internals, from this recorded build. They can change in any release. What's promised is the behavior the docs describe: "use client" marks where client code starts, props that cross must be serializable, and Server Functions are called with a POST. Never read these files or ids from your app's code.

What can cross, and where React checks

A prop from a Server Component to a Client Component goes into the RSC payload, so it must be serializable: React must be able to write it as text and read it back. Composing Server and Client Components listed what crosses. Here is where React decides for functions, in ReactFlightServer.js, lines 4292 to 4303 and 4349 to 4356:

Functions in props, shortened from ReactFlightServer.jsJavaScript
if (typeof value === 'function') {
  if (isClientReference(value)) {
    return serializeClientReference(
      request,
      parent,
      parentPropertyName,
      value as any,
    );
  }
  if (isServerReference(value)) {
    return serializeServerReference(request, value as any);
  }
// …
  } else {
    throw new Error(
      'Functions cannot be passed directly to Client Components ' +
        'unless you explicitly expose it by marking it with "use server". ' +
        'Or maybe you meant to call this function rather than return it.' +
        describeObjectForErrorMessage(parent, parentPropertyName),
    );
  }

Lines 2 to 9: a client reference, like AddToCart itself, becomes an import row. Lines 10 to 12: a server reference, like addToCart, becomes its id. Every other function reaches line 15 and throws. (Between them, in the lines left out, a function whose prop name starts with on gets its own message, at lines 4327 to 4332.) So the two references aren't only how code is found: they're the only functions allowed to cross. Try the four versions:

Playground: which functions can cross to a Client Component?
The Mint tea page, with this versionNext.js
app/
app/add-to-cart.tsx
"use client"; import { useState } from "react";import { addToCart } from "./actions"; export default function AddToCart({ tea }: { tea: string }) {  const [added, setAdded] = useState(false);  async function add() {    await addToCart(tea);    setAdded(true);  }  return (    <button onClick={add} disabled={added}>      {added ? "Added to cart" : "Add to cart"}    </button>  );} export function CartBadge() {  return <span>Your cart</span>;}
Recorded from Next.js 16.4.3
$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled- Experiments (use with caution):  ⨯ turbopackMinify   Creating an optimized production build ...✓ Compiled successfully  Running TypeScript ...  Finished TypeScript ...  Collecting page data ...✓ Generating static pages (3/3)  Finalizing page optimization ... Route (app)┌ ○ /└ ○ /_not-found ○  (Static)  prerendered as static content $ next start▲ Next.js 16.4.3- Local:         http://127.0.0.1:3000- Network:       http://127.0.0.1:3000✓ ReadyaddToCart ran on the server for mint
Try it yourself
Imported Server Function

app/add-to-cart.tsx imports addToCart from a "use server" file. The build gives the browser a stub with the action's id; the Steps tab shows the click's POST with a next-action header.

Every version was built with next build from Next.js 16.4.3; the Terminal shows what it printed, and for the versions that built, what next start printed after the click. Open Steps to see the click's request. Reset goes back to the first version.

The ordinary function is this page:

app/page.tsx with an ordinary function propTSX
import AddToCart from "./add-to-cart";

export default function MintPage() {
  return (
    <main>
      <h1>Mint tea</h1>
      <AddToCart tea="mint" addAction={async (tea: string) => console.log(tea)} />
    </main>
  );
}

Line 7 passes an arrow function as addAction. The build stopped while it prerendered /, with the error from line 16 of the excerpt and the props React was writing, {tea: "mint", addAction: function addAction}. Adding "use server" to that function, or moving it into actions.ts, makes it a server reference, and it crosses as an id.

Finding the references in your own build

In a Next.js 16.4.3 project built with Turbopack and run on Node.js, you can read all of this yourself. Build it once with the minifier off, as the Mint tea example does, and search .next. The paths and shapes below are this version's; other versions, webpack builds and the Edge runtime differ. Read them to learn and to debug, never from your app's code:

  • The RSC copy of a client file: search .next/server/chunks/ssr for registerClientReference and the file's path.
  • Its browser copy: search .next/static/chunks for a text the component shows, like Added to cart.
  • The manifests: .next/server/app/<page>_client-reference-manifest.js for each page, and .next/server/server-reference-manifest.json for the whole app.
  • An action id: the next-action header in your browser's Network tab, after a click that calls a Server Function.

Sort what you might find: where does each piece live?

Which part of the build holds it?
  • registerClientReference(…, "[project]/app/add-to-cart.tsx", "default")
  • registerServerReference(addToCart, id, null)
  • useState(false) inside AddToCart
  • createServerReference(id, callServer, …)
  • "[project]/app/add-to-cart.tsx": { "id": …, "chunks": […] }
  • "<action id>": { "workers": { "app/page": { "moduleId": … } } }
Try it yourself
0 of 6 correct

Sort each piece of the recorded build: the RSC copy on the server, the client copy (browser and SSR), or a manifest.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "'use client' makes a file run only in the browser." No. The build makes an SSR copy too, and the server runs it for the first HTML. Only Server Components see a reference instead.
  • "The server sends the component's code in the payload." No. The payload holds an import row: a module id, chunk addresses and an export's name. The browser downloads the chunks separately.
  • "A Server Function's code is in the browser's chunk, hidden." No. The browser's chunk has a stub made by createServerReference, with the id. The function's code stays in the RSC layer's module.
  • "An action id is a secret, so the action is safe." No. Every visitor whose page uses the action gets its id. A Server Function is a public endpoint and checks who is asking itself.
  • "The browser loads react-server-dom-webpack in every Next.js app." No. A Turbopack build uses react-server-dom-turbopack on both sides; the webpack name is aliased to it.
  • "Any function can be passed to a Client Component." No. Only client references and server references cross. Anything else stops the render with "Functions cannot be passed directly to Client Components".

Practice exercises

These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints. Some ask you to find a line in Next.js's source, and the links above point at it.

Exercise 1 · Warm-upWhich exports get a reference?

This is app/add-to-cart.tsx. Which export names does its RSC copy pass to registerClientReference?

Starter codeTSX
"use client";

import { useState } from "react";
import { addToCart } from "./actions";

export default function AddToCart({ tea }: { tea: string }) {
  const [added, setAdded] = useState(false);
  async function add() {
    await addToCart(tea);
    setAdded(true);
  }
  return (
    <button onClick={add} disabled={added}>
      {added ? "Added to cart" : "Add to cart"}
    </button>
  );
}

export function CartBadge() {
  return <span>Your cart</span>;
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upRead the import row

    This row is from the recorded payload of the Mint tea page. Which export of the module does the browser use?

    Starter codeText
    8:I[7870,["$2","$7"],"default"]

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind it in the source: a CommonJS client file

      A client file is written in CommonJS, with module.exports. Which React function does its RSC copy call?

      Starter codeRust
      } else {
          is_esm = false;
          writedoc!(
              code,
              r#"
                  // This file is generated by next-core EcmascriptClientReferenceModule.
                  const {{ createClientModuleProxy }} = require("react-server-dom-turbopack/server");
      
                  {TURBOPACK_EXPORT_NAMESPACE}(createClientModuleProxy({server_module_path}));
              "#,

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFollow the next-action header

        This is the click's request from the recording. Which built file does the server read to find the function for this id?

        Starter codeText
        POST /
        next-action: 404c9eadfada699aa00315b9ad2b9fd234acedc2ca

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePredict the build: an event handler

          The page passes onAdded to a Client Component that uses it as its button's onClick. What does next build print after Error:?

          Starter codeTSX
          import AddToCart from "./add-to-cart";
          
          export default function MintPage() {
            return (
              <main>
                <h1>Mint tea</h1>
                <AddToCart tea="mint" onAdded={() => console.log("added")} />
              </main>
            );
          }
          

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeWhere is the stub?

            In the version where the page passes addToCart as addAction, this is the Client Component. Which generated client module hard-codes a createServerReference call with this action's id?

            Starter codeTSX
            "use client";
            
            import { useState } from "react";
            
            export default function AddToCart({ tea, addAction }: { tea: string; addAction: (tea: string) => Promise<void> }) {
              const [added, setAdded] = useState(false);
              async function add() {
                await addAction(tea);
                setAdded(true);
              }
              return (
                <button onClick={add} disabled={added}>
                  {added ? "Added to cart" : "Add to cart"}
                </button>
              );
            }
            

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask which layer the code is in, and which manifest turns an id back into code.

              The client and server references quiz · 7 questionsScore: first tries count
              1. Question 1 of 7A Server Component imports AddToCart from a "use client" file. What does the import give it, in the recorded build?

                Choose an answer to see the explanation.

              2. Question 2 of 7In the recorded page_client-reference-manifest.js, what does ssrModuleMapping map?

                Choose an answer to see the explanation.

              3. Question 3 of 7In a Next.js 16.4.3 app built with Turbopack, which package renders the RSC payload on the server?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does the browser send when the recorded button calls addToCart?

                Choose an answer to see the explanation.

              5. Question 5 of 7What happens when next build prerenders this page?

                Read the code, then predictTSX
                import AddToCart from "./add-to-cart";
                
                export default function MintPage() {
                  return (
                    <main>
                      <h1>Mint tea</h1>
                      <AddToCart tea="mint" addAction={async (tea: string) => console.log(tea)} />
                    </main>
                  );
                }
                

                Choose an answer to see the explanation.

              6. Question 6 of 7Why is Next.js's own global-error.js registered with createClientModuleProxy instead of registerClientReference?

                Choose an answer to see the explanation.

              7. Question 7 of 7Which of these crosses from the page to AddToCart as a prop?

                Read the code, then predictTSX
                import AddToCart from "./add-to-cart";
                
                export default function MintPage() {
                  return (
                    <main>
                      <h1>Mint tea</h1>
                      <AddToCart tea="mint" onAdded={() => console.log("added")} />
                    </main>
                  );
                }
                

                Choose an answer to see the explanation.

              Key takeaways

              • The build makes three copies of a "use client" file: an RSC copy that is only a proxy, an SSR copy for the first HTML, and a browser copy. Each layer imports its own.
              • The proxy calls registerClientReference(throwingFunction, path, exportName) for each export, or createClientModuleProxy(path) for a module that isn't an ES module. React writes an import row for each reference that renders.
              • The client reference manifest maps a file's path to the browser's module and chunks, and that module to the SSR and RSC copies.
              • A "use server" function keeps its code in the RSC layer, registered with an action id. The client side gets a createServerReference stub, and the server finds the module in server-reference-manifest.json.
              • Under Turbopack, Next.js uses react-server-dom-turbopack on both sides; webpack builds use react-server-dom-webpack. Both come with Next.js.
              • Only client and server references can cross as functions. Every other function prop stops the render.

              Remember the one-liner.
              Directives become references: ids on one side, code on the other, joined by the manifests the build writes.

              Coming next: How a route becomes a React tree. It follows a request into app-render.tsx, which builds the tree from your layouts and pages and renders it to the RSC payload, then HTML.

              CompleteFrontend Clear concepts. Working examples.