remix-run/react-router · error · Error
Could not parse TypeScript
Error message
Could not parse TypeScript
What it means
Thrown by the transpile() helper in useJavascript.ts when babel.transformSync returns null or produces empty code for the input tsx string. The transform uses @babel/preset-typescript (with jsx: 'preserve') plus @babel/plugin-syntax-jsx. An empty result means Babel could not produce output — typically because the input is empty, the filename/cwd options resolve to a non-existent directory, or a Babel internals failure occurred.
Source
Thrown at packages/react-router-dev/cli/useJavascript.ts:23
import babelPresetTypeScript from "@babel/preset-typescript";
import prettier from "prettier";
export async function transpile(
tsx: string,
options: {
cwd?: string;
filename?: string;
} = {},
): Promise<string> {
let mjs = babel.transformSync(tsx, {
compact: false,
cwd: options.cwd,
filename: options.filename,
plugins: [babelPluginSyntaxJSX],
presets: [[babelPresetTypeScript, { jsx: "preserve" }]],
retainLines: true,
});
if (!mjs || !mjs.code) throw new Error("Could not parse TypeScript");
/**
* Babel's `compact` and `retainLines` options are both bad at formatting code.
* Use Prettier for nicer formatting.
*/
return await prettier.format(mjs.code, { parser: "babel" });
}
View on GitHub (pinned to 1fd704a7da)
Solutions
- Ensure the input string is non-empty and actually TypeScript/JSX before calling transpile.
- Verify @babel/core, @babel/preset-typescript, and @babel/plugin-syntax-jsx are installed and version-compatible (reinstall node_modules).
- If passing options.cwd/options.filename, confirm the directory exists and contains the relevant babel config or that none is required.
- Run the build with verbose output to capture any swallowed Babel exception preceding the null result.
- Wrap the call site in a try/catch and log babel.transformSync's return value to diagnose why code is empty.
Example fix
// before
const js = await transpile(maybeEmptyString, { cwd: '/missing' });
// after
defensiveTrimAndCheck();
if (!tsx.trim()) throw new Error('nothing to transpile');
const js = await transpile(tsx, { cwd: process.cwd(), filename: 'entry.tsx' }); Defensive patterns
Strategy: validation
Validate before calling
import * as babel from '@babel/core';
function canTranspile(tsx: string, cwd?: string, filename?: string): boolean {
if (!tsx || !tsx.trim()) return false;
try {
const r = babel.transformSync(tsx, { cwd, filename, compact: false,
plugins: [require('@babel/plugin-syntax-jsx')],
presets: [[require('@babel/preset-typescript'), { jsx: 'preserve' }]],
retainLines: true });
return !!r && !!r.code;
} catch { return false; }
}
// if (!canTranspile(source)) surface a clearer error before continuing Type guard
function isNonEmptyString(s: unknown): s is string {
return typeof s === 'string' && s.trim().length > 0;
} Try / catch
try {
const js = await transpile(tsx, { cwd, filename });
} catch (e) {
if (/Could not parse TypeScript/.test(String((e as Error).message))) {
// log tsx length, cwd, filename; verify @babel/* versions; fail build with context
throw new Error(`transpile() returned no output for ${filename ?? '<inline>'}`);
}
throw e;
} Prevention
- Never pass empty/whitespace-only strings to transpile().
- Pin and reinstall @babel/core, @babel/preset-typescript, @babel/plugin-syntax-jsx together.
- Resolve cwd/filename to existing directories before calling.
- Add a unit test that transpiles a known snippet to catch environment regressions.
When it happens
Trigger: Calling transpile('') or transpile(undefined as any); passing options.cwd or options.filename pointing at a missing path so Babel cannot resolve config/plugins; a Babel plugin/preset version mismatch where transformSync throws internally but is caught upstream leaving mjs null.
Common situations: An empty routes.ts or entry file passed for JS conversion during build; a stale node_modules with mismatched @babel/core and preset-typescript versions; a build script invoking transpile before the source file is written; misconfigured monorepo hoisting breaking Babel resolution.
Related errors
- React Router presets must have a `name` property defined.
- Could not find package.json in ${rootDirectory} or any of it
- Unable to define routes with duplicate route id: "${id}"
- React Router Vite plugin not found in Vite config
- Custom Vite manifest paths are not supported
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/1e129eecc7e9d512.
Report an issue: GitHub.