facebook/react · error · Error

Expected source to have been transformed to a string.

Error message

Expected source to have been transformed to a string.

What it means

In the loader's transformSource hook, the source returned by defaultTransformSource for format 'module' must be a plain string, because React parses it (directive scan, client-import rewriting) with its own tooling. Node permits hook results of string, ArrayBuffer, or Uint8Array; any binary representation that reaches this point fails the invariant.

Source

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

  }

  return transformServerModule(source, program, url, sourceMap, loader);
}

export async function transformSource(
  source: Source,
  context: TransformSourceContext,
  defaultTransformSource: TransformSourceFunction,
): Promise<{source: Source}> {
  const transformed = await defaultTransformSource(
    source,
    context,
    defaultTransformSource,
  );
  if (context.format === 'module') {
    const transformedSource = transformed.source;
    if (typeof transformedSource !== 'string') {
      throw new Error('Expected source to have been transformed to a string.');
    }
    const newSrc = await transformModuleIfNeeded(
      transformedSource,
      context.url,
      (url: string, ctx: LoadContext, defaultLoad: LoadFunction) => {
        return loadClientImport(url, defaultTransformSource);
      },
    );
    return {source: newSrc};
  }
  return transformed;
}

export async function load(
  url: string,
  context: LoadContext,
  defaultLoad: LoadFunction,
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {

View on GitHub (pinned to eafeac097b)

Solutions

  1. Fix the earlier loader so JS module sources are returned as utf-8 strings
  2. Insert a small adapter hook before React's that decodes binary sources with new TextDecoder().decode(source) for format 'module'
  3. Drop --experimental-network-imports or other schemes that yield binary module sources

Example fix

// before — chained loader hands React a Buffer for module format
export async function transformSource(source, context, dflt) {
  const r = await dflt(source, context, dflt);
  return r; // source is a Uint8Array here
}

// after — always hand React a string for module format
export async function transformSource(source, context, dflt) {
  const r = await dflt(source, context, dflt);
  if (context.format === 'module' && typeof r.source !== 'string') {
    return {...r, source: new TextDecoder().decode(r.source)};
  }
  return r;
}
Defensive patterns

Strategy: validation

Validate before calling

// in any chained hook you control, enforce the invariant before React's hook runs
export async function transformSource(source, context, dflt) {
  const r = await dflt(source, context, dflt);
  if (context.format === 'module' && typeof r.source !== 'string') {
    return {...r, source: new TextDecoder().decode(r.source)};
  }
  return r;
}

Type guard

export function isStringModuleSource(result) {
  return result.format !== 'module' || typeof result.source === 'string';
}

Try / catch

try {
  await import(url);
} catch (e) {
  if (/transformed to a string/.test(e.message)) {
    console.error('A chained loader returned binary source for', url);
  }
  throw e;
}

Prevention

When it happens

Trigger: A loader earlier in the chain returns {format: 'module', source: Buffer|Uint8Array|ArrayBuffer}, or the module comes from a scheme whose source Node produces as binary (for example network imports under --experimental-network-imports), and it flows into transformSource.

Common situations: Chaining asset or transpilation loaders (tsx, esbuild, CSS loaders) that return Buffers; Node major upgrades changing default source types; data: or http: module imports.

Related errors


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