facebook/react · error · Error
Expected the transformed source to be a string.
Error message
Expected the transformed source to be a string.
What it means
While handling `export * from ...` declarations, the RSC ESM loader recursively loads the re-exported module through the loader chain and expects result.source to be a string before parsing it with acorn to enumerate export names. Node's load hooks may legitimately return ArrayBuffer sources under newer protocol versions, and chained loaders can return other shapes; anything non-string hits this throw.
Source
Thrown at packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js:530
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);
continue;
} else {
const {url} = await resolveClientImport(node.source.value, parentURL);
const {source} = await loader(
url,
{format: 'module', conditions: [], importAssertions: {}},
loader,
);
if (typeof source !== 'string') {
throw new Error('Expected the transformed source to be a string.');
}
let childBody;
try {
childBody = acorn.parse(source, {
ecmaVersion: '2024',
sourceType: 'module',
}).body;
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
continue;
}
await parseExportNamesInto(childBody, names, url, loader);
continue;
}
case 'ExportDefaultDeclaration':
names.push('default');
continue;View on GitHub (pinned to eafeac097b)
Solutions
- Replace `export * from './client-mod'` with explicit named re-exports so the loader never needs to load and parse the child module
- Ensure every loader in the chain normalizes source to a string (convert ArrayBuffer with new TextDecoder().decode(source) before returning)
- Match react-server-dom-esm to a Node version its loader supports; check the package release notes for loader fixes
Example fix
// before (barrel re-export in a client boundary)
export * from './buttons';
// after (explicit names)
export {Button, LinkButton} from './buttons'; Defensive patterns
Strategy: validation
Validate before calling
// for loader-chain authors: normalize before returning
async function load(url, context, nextLoad) {
const result = await nextLoad(url, context);
if (result.source != null && typeof result.source !== 'string') {
result.source = new TextDecoder().decode(result.source);
}
return result;
} Prevention
- Prefer explicit named re-exports over `export *` in modules that cross the client/server boundary
- Keep loaders in the chain returning string sources
- Match react-server-dom-esm and Node versions per the package's supported matrix
When it happens
Trigger: A client-reference module that uses `export * from './other'` where the chained loader (or Node's default load for certain URL schemes/versions) returns an ArrayBuffer or null source; loaders like tsx/esbuild-register in the chain returning non-string sources; Node version drift in the load-hook contract under this package's supported matrix.
Common situations: Monorepos whose server graphs include barrel files re-exporting client components; combining the RSC loader with TypeScript loaders on newer Node; data: URLs or exotic loaders feeding non-string sources into the chain.
Related errors
- Expected resolve to have been called before transformSource
- 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/928d7829ba8ebec3.
Report an issue: GitHub.