facebook/react · error · Error
Expected resolve to have been called before transformSource
Error message
Expected resolve to have been called before transformSource
What it means
The RSC ESM loader implements Node's ESM hooks: during its resolve hook it stashes the next resolve hook from the chain (stashedResolve). transformSource later needs it inside resolveClientImport() to re-resolve `export * from` specifiers the way a client would. If transformSource runs before this loader's resolve hook ever ran (stashedResolve === null), the loader's ordering invariant was broken and it throws.
Source
Thrown at packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js:502
return;
case 'ParenthesizedExpression':
addExportNames(names, node.expression);
return;
}
}
function resolveClientImport(
specifier: string,
parentURL: string,
): {url: string} | Promise<{url: string}> {
// Resolve an import specifier as if it was loaded by the client. This doesn't use
// the overrides that this loader does but instead reverts to the default.
// This resolution algorithm will not necessarily have the same configuration
// as the actual client loader. It should mostly work and if it doesn't you can
// always convert to explicit exported names instead.
const conditions = ['node', 'import'];
if (stashedResolve === null) {
throw new Error(
'Expected resolve to have been called before transformSource',
);
}
return stashedResolve(specifier, {conditions, parentURL}, stashedResolve);
}
async function parseExportNamesInto(
body: any,
names: Array<string>,
parentURL: string,
loader: LoadFunction,
): Promise<void> {
for (let i = 0; i < body.length; i++) {
const node = body[i];
switch (node.type) {
case 'ExportAllDeclaration':
if (node.exported) {
addExportNames(names, node.exported);View on GitHub (pinned to eafeac097b)
Solutions
- Register the full loader module exactly as documented (node --experimental-loader or module.register on the complete loader), so both its resolve and transformSource hooks engage
- Ensure any custom resolve hooks in the chain forward to nextResolve instead of returning early
- Never call the loader's transformSource export manually; drive everything through Node's module system
- Check the react-server-dom-esm version against your Node version — the loader hook contract changed across Node 18/20/22
Example fix
// before (custom harness calling the hook directly)
import {transformSource} from 'react-server-dom-esm/npm/loader.js';
const r = await transformSource(url, source, format); // throws
// after (register the whole loader)
import {register} from 'node:module';
register('react-server-dom-esm/npm/loader.js', import.meta.url); Defensive patterns
Strategy: validation
Validate before calling
// correct registration ensures resolve runs before transformSource
// server-entry.mjs
import {register} from 'node:module';
register('react-server-dom-esm/npm/loader.js', import.meta.url);
// then start: node --conditions react-server server-entry.mjs Prevention
- Register the complete loader module via module.register or --experimental-loader; never call its hooks directly
- Make custom resolve hooks in the chain always forward via nextResolve
- Pin the react-server-dom-esm version to one tested against your Node release
When it happens
Trigger: A hand-built or mis-chained loader setup where a preceding resolve hook short-circuits without calling nextResolve, so this loader's resolve never runs; invoking the loader's transformSource export directly instead of going through Node's module system; module runners or test harnesses that call transform hooks without resolve; Node loader-API version drift changing hook invocation order.
Common situations: Combining react-server-dom-esm's loader with other --experimental-loader entries (tsx, ts-node) that swallow the chain; upgrading Node and re-wiring module.register() incorrectly; copying the loader into a custom server harness that bypasses resolve.
Related errors
- Expected the transformed source to be a string.
- Use react-server-dom-esm/client instead.
- The source map has more mappings than there are lines.
- The module "${modulePath}" is marked as an async ESM module
- The source map has more mappings than there are lines.
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/cbe9fd4680fd1318.
Report an issue: GitHub.