facebook/react · error · Error

Invalid server action: ${ref}

Error message

Invalid server action: ${ref}

What it means

On the Parcel client, resolveServerReference splits a serialized id '<moduleId>#<exportName>' and requires bundlerConfig[moduleId] — the Server Manifest entry describing which bundles to load. The manifest is the second argument passed to createFromFetch/createFromNodeStream/createFromReadableStream. An unknown id means the client cannot locate the module implementing the action, so the call is rejected before any request is made.

Source

Thrown at packages/react-server-dom-parcel/src/client/ReactFlightClientConfigBundlerParcel.js:62

export function resolveClientReference<T>(
  bundlerConfig: null,
  metadata: ClientReferenceMetadata,
): ClientReference<T> {
  // Reference is already resolved during the build.
  return metadata;
}

export function resolveServerReference<T>(
  bundlerConfig: ServerManifest,
  ref: ServerReferenceId,
): ClientReference<T> {
  const idx = ref.lastIndexOf('#');
  const id = ref.slice(0, idx);
  const name = ref.slice(idx + 1);
  const bundles = bundlerConfig[id];
  if (!bundles) {
    throw new Error('Invalid server action: ' + ref);
  }
  return [id, name, bundles];
}

export function preloadModule<T>(
  metadata: ClientReference<T>,
): null | Thenable<any> {
  if (metadata[IMPORT_MAP]) {
    parcelRequire.extendImportMap(metadata[IMPORT_MAP]);
  }

  if (metadata[BUNDLES].length === 0) {
    return null;
  }

  return Promise.all(metadata[BUNDLES].map(url => parcelRequire.load(url)));
}

View on GitHub (pinned to eafeac097b)

Solutions

  1. Rebuild so the server manifest includes the new action's module id, and pass that manifest as the second argument to createFrom*
  2. Verify the id before calling: const id = ref.slice(0, ref.lastIndexOf('#')); if (!manifest[id]) the manifest is stale
  3. Make the payload and the manifest come from the same build artifact so ids never drift

Example fix

// before — manifest predates the action
const data = createFromFetch(fetch('/endpoint'), staleServerManifest);
await data.saveNote(); // Invalid server action: notes/saveNote#saveNote

// after — payload and manifest ship from the same build
const build = await import(`./build/manifest-${BUILD_ID}.js`);
const data = createFromFetch(fetch('/endpoint'), build.serverManifest);
await data.saveNote();
Defensive patterns

Strategy: validation

Validate before calling

export function isKnownServerAction(manifest, ref) {
  const idx = ref.lastIndexOf('#');
  return idx > 0 && Object.prototype.hasOwnProperty.call(manifest, ref.slice(0, idx));
}
// before invoking: if (!isKnownServerAction(serverManifest, id)) { refresh the manifest or surface an error }

Type guard

export function parseServerReference(ref) {
  const idx = ref.lastIndexOf('#');
  return idx > 0 ? {id: ref.slice(0, idx), name: ref.slice(idx + 1)} : null;
}

Try / catch

try {
  await action(...args);
} catch (e) {
  if (/Invalid server action/.test(e.message)) {
    // manifest drift — reload the manifest for the current build and retry once
  }
  throw e;
}

Prevention

When it happens

Trigger: Invoking a server function whose module id is absent from the manifest passed to createFrom*; a ref without a '#' so the parsed id is garbage; a manifest generated by an older build than the payload that references the action.

Common situations: Adding a new server action but shipping a stale client manifest; build pipelines emitting client and server artifacts from different revisions; passing the client-reference manifest where the server manifest belongs.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/48b5c106c7afd584. Report an issue: GitHub.