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
- 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
- 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
- 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
- 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
- Register the loader in a single bootstrap file as the first statement, before any static app import
- Pin the same Node major in dev, CI, and production
- Never chain loaders that do not delegate to their default* hooks
- Add a CI smoke test that imports a module containing a client reference through the loader
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
- Expected source to have been transformed to a string.
- Expected source to have been loaded into a string.
- The React Server cannot be used outside a react-server envir
- The React Server Writer cannot be used outside a react-serve
- The React Server Writer cannot be used outside a react-serve
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/b273bc59aa4e729e.
Report an issue: GitHub.