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

  1. Ensure the input string is non-empty and actually TypeScript/JSX before calling transpile.
  2. Verify @babel/core, @babel/preset-typescript, and @babel/plugin-syntax-jsx are installed and version-compatible (reinstall node_modules).
  3. If passing options.cwd/options.filename, confirm the directory exists and contains the relevant babel config or that none is required.
  4. Run the build with verbose output to capture any swallowed Babel exception preceding the null result.
  5. 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

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


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/1e129eecc7e9d512. Report an issue: GitHub.