facebook/react · error · Error
Expected source to have been loaded into a string.
Error message
Expected source to have been loaded into a string.
What it means
The loader's load hook requires that defaultLoad's result for format 'module' carry a string source, because transformModuleIfNeeded then parses the source for 'use client'/'use server' directives and rewrites client-reference imports. Node legally hands loaders ArrayBuffer or Uint8Array sources, and any non-string reaching this check throws.
Source
Thrown at packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js:794
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}> {
const result = await defaultLoad(url, context, defaultLoad);
if (result.format === 'module') {
if (typeof result.source !== 'string') {
throw new Error('Expected source to have been loaded into a string.');
}
const newSrc = await transformModuleIfNeeded(
result.source,
url,
defaultLoad,
);
return {format: 'module', source: newSrc};
}
return result;
}
View on GitHub (pinned to eafeac097b)
Solutions
- Fix the earlier loader so JS module sources are returned as utf-8 strings before React's load hook sees them
- Add an adapter load hook that converts binary module sources with new TextDecoder().decode(source)
- Avoid importing modules from schemes that produce binary sources
Example fix
// before — chained loader returns a Buffer
export async function load(url, context, dflt) {
const r = await dflt(url, context, dflt);
return r; // r.source is a Buffer for .js files
}
// after — decode module sources to strings
export async function load(url, context, dflt) {
const r = await dflt(url, context, dflt);
if (r.format === 'module' && typeof r.source !== 'string') {
return {...r, source: new TextDecoder().decode(r.source)};
}
return r;
} Defensive patterns
Strategy: validation
Validate before calling
// adapter load hook placed in front of React's loader
export async function load(url, context, dflt) {
const r = await dflt(url, context, dflt);
if (r.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 (/loaded into a string/.test(e.message)) {
console.error('Binary module source for', url, '— decode it in a preceding load hook');
}
throw e;
} Prevention
- Return strings for JS module sources from every custom load hook
- Test the full loader chain against a 'use client' module in CI
- Watch Node release notes for changes to default source types
When it happens
Trigger: defaultLoad (or an earlier loader in the chain) returns {format: 'module', source: ArrayBuffer|Uint8Array|Buffer} — for example a chained loader returning Buffers, or a URL scheme that Node loads as binary while still reporting format 'module'.
Common situations: Chained loaders (asset/WASM/CSS loaders returning buffers); --experimental-network-imports; Node upgrades that change the default source representation for some schemes.
Related errors
- Expected source to have been transformed to a string.
- Expected getSource to have been called before transformSourc
- react-dom/client is not supported in React Server Components
- react-dom/profiling is not supported in React Server Compone
- react-dom/server is not supported in React Server Components
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/fe8b95a042e33c2f.
Report an issue: GitHub.