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

  1. 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
  2. Ensure any custom resolve hooks in the chain forward to nextResolve instead of returning early
  3. Never call the loader's transformSource export manually; drive everything through Node's module system
  4. 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

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


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