remix-run/remix · error · Error

Unsupported tsconfig compilerOptions.jsx = "${jsx}" for ${fi

Error message

Unsupported tsconfig compilerOptions.jsx = "${jsx}" for ${filePath}. remix/node-tsx must compile JSX to runnable JavaScript.

What it means

remix/node-tsx compiles TSX files to runnable JavaScript using your tsconfig compilerOptions. The `jsx` option is set to `preserve` (or `react-native`), which leaves JSX syntax in the output instead of transforming it — output Node cannot execute. The transformer refuses to proceed because the compiled result would crash at runtime.

Source

Thrown at packages/node-tsx/src/lib/transform.ts:159

  if (value === null) {
    return 'null'
  }

  return typeof value
}

function getJsxTransformOptions(
  filePath: string,
  compilerOptions?: TsconfigTransformCompilerOptions,
): Pick<TransformOptions, 'jsx'> {
  let jsx = compilerOptions?.jsx
  let importSource = compilerOptions?.jsxImportSource
  let factory = compilerOptions?.jsxFactory
  let fragment = compilerOptions?.jsxFragmentFactory

  if (jsx === 'preserve' || jsx === 'react-native') {
    throw new Error(
      `Unsupported tsconfig compilerOptions.jsx = "${jsx}" for ${filePath}. ` +
        'remix/node-tsx must compile JSX to runnable JavaScript.',
    )
  }

  if (jsx == null || jsx === 'react-jsx' || jsx === 'react-jsxdev') {
    return {
      jsx: {
        development: jsx === 'react-jsxdev',
        importSource,
        runtime: 'automatic',
      },
    }
  }

  return {
    jsx: {
      pragma: factory,

View on GitHub (pinned to 9696913134)

Solutions

  1. Set `"jsx": "react-jsx"` (recommended) or `"jsx": "react-jsxdev"`/`"react"` in the tsconfig that applies to the file
  2. Check `tsconfig.node.json`/extended configs in monorepos for an overriding `jsx: preserve`
  3. If you intentionally preserve JSX for another bundler, exclude those files from node-tsx loading or use a separate tsconfig for the runtime

Example fix

// before (tsconfig.json)
{ "compilerOptions": { "jsx": "preserve" } }

// after
{ "compilerOptions": { "jsx": "react-jsx" } }
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs'

let tsconfig = JSON.parse(fs.readFileSync('tsconfig.json', 'utf8'))
let jsx = tsconfig.compilerOptions?.jsx
if (jsx === 'preserve' || jsx === 'react-native') {
  throw new Error('Fix tsconfig jsx before running under remix/node-tsx')
}

Type guard

null

Prevention

When it happens

Trigger: A .tsx file is loaded through remix/node-tsx while the effective tsconfig sets `"jsx": "preserve"` or `"jsx": "react-native"`. Can also happen when an editor/IDE auto-generates a tsconfig or when a tool writes jsx: preserve for type-only configs.

Common situations: Teams that keep `jsx: preserve` for a separate bundler pass (Vite/webpack handle JSX) but then run the same files directly under the Remix node-tsx runtime; monorepos where a root tsconfig overrides the app tsconfig; newly scaffolded projects copying a type-check-only tsconfig.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/987ba75e34bcf471. Report an issue: GitHub.