{"record":{"id":"987ba75e34bcf471","repo":"remix-run/remix","slug":"unsupported-tsconfig-compileroptions-jsx-jsx","errorCode":null,"errorMessage":"Unsupported tsconfig compilerOptions.jsx = \"${jsx}\" for ${filePath}. remix/node-tsx must compile JSX to runnable JavaScript.","messagePattern":"Unsupported tsconfig compilerOptions\\.jsx = \"(.+?)\" for (.+?)\\. remix/node-tsx must compile JSX to runnable JavaScript\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/node-tsx/src/lib/transform.ts","lineNumber":159,"sourceCode":"\n  if (value === null) {\n    return 'null'\n  }\n\n  return typeof value\n}\n\nfunction getJsxTransformOptions(\n  filePath: string,\n  compilerOptions?: TsconfigTransformCompilerOptions,\n): Pick<TransformOptions, 'jsx'> {\n  let jsx = compilerOptions?.jsx\n  let importSource = compilerOptions?.jsxImportSource\n  let factory = compilerOptions?.jsxFactory\n  let fragment = compilerOptions?.jsxFragmentFactory\n\n  if (jsx === 'preserve' || jsx === 'react-native') {\n    throw new Error(\n      `Unsupported tsconfig compilerOptions.jsx = \"${jsx}\" for ${filePath}. ` +\n        'remix/node-tsx must compile JSX to runnable JavaScript.',\n    )\n  }\n\n  if (jsx == null || jsx === 'react-jsx' || jsx === 'react-jsxdev') {\n    return {\n      jsx: {\n        development: jsx === 'react-jsxdev',\n        importSource,\n        runtime: 'automatic',\n      },\n    }\n  }\n\n  return {\n    jsx: {\n      pragma: factory,","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/node-tsx/src/lib/transform.ts#L141-L177","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set `\"jsx\": \"react-jsx\"` (recommended) or `\"jsx\": \"react-jsxdev\"`/`\"react\"` in the tsconfig that applies to the file","Check `tsconfig.node.json`/extended configs in monorepos for an overriding `jsx: preserve`","If you intentionally preserve JSX for another bundler, exclude those files from node-tsx loading or use a separate tsconfig for the runtime"],"exampleFix":"// before (tsconfig.json)\n{ \"compilerOptions\": { \"jsx\": \"preserve\" } }\n\n// after\n{ \"compilerOptions\": { \"jsx\": \"react-jsx\" } }","handlingStrategy":"validation","validationCode":"import fs from 'node:fs'\n\nlet tsconfig = JSON.parse(fs.readFileSync('tsconfig.json', 'utf8'))\nlet jsx = tsconfig.compilerOptions?.jsx\nif (jsx === 'preserve' || jsx === 'react-native') {\n  throw new Error('Fix tsconfig jsx before running under remix/node-tsx')\n}","typeGuard":"null","tryCatchPattern":null,"preventionTips":["Keep one tsconfig for type-checking (jsx: preserve ok) and ensure the runtime tsconfig uses react-jsx","Add a CI check that fails on jsx: preserve in the app tsconfig","Avoid editor auto-generated tsconfigs overriding yours"],"tags":["node-tsx","tsx","tsconfig","jsx"],"backgroundTag":"invalid-tsconfig-compiler-option","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}