Files ending in .jsx and .tsx execute through the same load hook. JSX is recognized in .jsx / .tsx only, never in plain .js. The defaults are the modern ones — automatic runtime, react as the import source — matching oxc-transformer, Vite's React plugins, Rolldown, and Bun, so a React project needs no setup.

nub render.tsx

compilerOptions.jsx

Configure the JSX runtime through tsconfig.json, the way the rest of your toolchain already does. These compilerOptions are honored:

  • jsx — the runtime mode. One of:

    "preserve" | "react" | "react-jsx" | "react-jsxdev" | "react-native"
  • jsxImportSource — the package the automatic runtime imports from (e.g. preact).

  • jsxFactory — the factory function for the classic runtime.

  • jsxFragmentFactory — the fragment factory for the classic runtime.

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

Preact, Hono, and Vue JSX work via passthrough. Solid is the exception — its JSX needs babel-preset-solid's reactive-graph compilation, which a per-file transpiler can't do, so run Solid through its bundler (e.g. nub run vite).

@jsxImportSource

A per-file /** @jsxImportSource ... */ pragma overrides the tsconfig import source for that one file. This is the standard mechanism for mixing JSX runtimes in a single project — a Hono route alongside React components.

/** @jsxImportSource hono/jsx */
export default function Page() {
  return <h1>Hello</h1>;
}

The pragma is read from the file source itself; no tsconfig entry is needed for it to take effect.