facebook/react · error · Error

Expected getSource to have been called before transformSourc

Error message

Expected getSource to have been called before transformSource

What it means

react-server-dom-esm ships a Node module-customization loader (package export 'react-server-dom-esm/node-loader'). Its getSource hook stashes the chain's default getSource so that later, when transformSource rewrites a module that imports a 'use client' reference, loadClientImport can re-fetch that imported module's raw source. This error means transformSource ran while stashedGetSource was still null: the loader's transformSource hook executed in a hook chain where its own getSource hook was never called.

Source

Thrown at packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js:630

        ) +
        ');';
    }
    newSrc += '},';
    newSrc += JSON.stringify(url) + ',';
    newSrc += JSON.stringify(name) + ');\n';
  }

  // TODO: Generate source maps for Client Reference functions so they can point to their
  // original locations.
  return newSrc;
}

async function loadClientImport(
  url: string,
  defaultTransformSource: TransformSourceFunction,
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
  if (stashedGetSource === null) {
    throw new Error(
      'Expected getSource to have been called before transformSource',
    );
  }
  // TODO: Validate that this is another module by calling getFormat.
  const {source} = await stashedGetSource(
    url,
    {format: 'module'},
    stashedGetSource,
  );
  const result = await defaultTransformSource(
    source,
    {format: 'module', url},
    defaultTransformSource,
  );
  return {format: 'module', source: result.source};
}

async function transformModuleIfNeeded(

View on GitHub (pinned to eafeac097b)

Solutions

  1. Register the loader exactly once via its documented entrypoint before any app import: node --conditions react-server --experimental-loader ./node_modules/react-server-dom-esm/esm/react-server-dom-esm-node-loader.production.js server.js, or register('react-server-dom-esm/node-loader', import.meta.url) from node:module
  2. Remove or reorder other loaders so React's getSource actually runs; every other loader in the chain must delegate to its default* function instead of short-circuiting
  3. Run a Node version supported by the loader; if your Node no longer calls getSource, make sure the loader's load hook path is used instead of transformSource
  4. Never invoke getSource/transformSource by hand; let Node drive the hook order for every module

Example fix

// before — a second loader swallows getSource, React's transformSource runs un-stashed
import {register} from 'node:module';
register('react-server-dom-esm/node-loader', import.meta.url);
register('tsx/esm', import.meta.url); // runs first and never delegates getSource
await import('./app.js');

// after — register React's loader so its hooks own the chain, app loads after
import {register} from 'node:module';
register('react-server-dom-esm/node-loader', import.meta.url);
await import('./app.js');
Defensive patterns

Strategy: validation

Validate before calling

// entry.mjs — the ONLY place the loader is registered; runs before any app import
import {register} from 'node:module';
register('react-server-dom-esm/node-loader', import.meta.url);

// Smoke-check the wiring: importing a module with a 'use client' import
// through the hooks throws here (in CI) instead of in production paths.
await import('./smoke-client-import.js');
await import('./app.js');

Try / catch

try {
  await import('./app.js');
} catch (e) {
  if (e instanceof Error && /Expected getSource/.test(e.message)) {
    console.error('React ESM loader not registered before app import');
  }
  throw e;
}

Prevention

When it happens

Trigger: Importing a module that contains a client-reference import through the ESM loader when the loader's getSource hook never ran: another loader in the chain short-circuits getSource without delegating to its default, hooks are registered in an order that skips React's getSource, the hooks are invoked manually instead of via --experimental-loader/register, or a Node version with a different hook protocol calls only part of the chain.

Common situations: Chaining --experimental-loader flags (tsx/esbuild plus React's loader) in the wrong order; registering the loader after app modules were already resolved; upgrading Node majors where getSource/transformSource semantics changed; test runners that bypass or partially apply the loader chain.

Related errors


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