Skip to content

React & JSX

React works out of the box. Vantris understands JSX natively and uses the automatic JSX runtime by default, so you don't need to import React. For stateful Fast Refresh — edits that update a component in place without losing its state — add the official @vantris/react plugin.

Zero-config JSX

Install the runtime packages and point your entry at a .tsx file:

bash
npm install react react-dom
tsx
// src/main.tsx
import { createRoot } from "react-dom/client";

function App() {
  return <h1>Hello Vantris</h1>;
}

createRoot(document.getElementById("app")!).render(<App />);
html
<!-- index.html -->
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>

That's a complete React app — no Babel config, no import React from "react".

Choosing the JSX runtime

The runtime is selected by your tsconfig.json:

tsconfig jsxRuntimeimport React?
"react-jsx"automaticnot needed
"react"classicrequired

For non-React JSX libraries (Preact, Emotion), set jsxImportSource:

jsonc
// tsconfig.json — Preact
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

Fast Refresh with @vantris/react

Native JSX gets you rendering, but a plain edit re-runs the whole module and resets component state. The @vantris/react plugin adds React Fast Refresh: a save updates the component in place and keeps its state (form inputs, counters, open/closed panels).

bash
npm install -D @vantris/react
ts
// vantris.config.ts
import { defineConfig } from "vantris";

export default defineConfig({
  plugins: ["@vantris/react"],
});

That's it. vantris dev now has Fast Refresh; vantris build compiles JSX as usual (the plugin steps aside in a build).

Plugins are referenced by string

Vantris loads plugins by string — a package name or a local path. To pass options, wrap the plugin in a tiny local module and reference that file instead. See With options.

What the plugin adds

  • Fast Refresh — component edits update in place, preserving state, over Vantris's HMR channel (dev only).
  • JSX via Babel — compiled with the automatic runtime by default; switch to classic or a custom jsxImportSource through the plugin's options.
  • Pre-bundling — seeds optimizeDeps.include with react, react-dom, react-dom/client, and the JSX runtimes, so the dev server is warm before the first request.

In a production build the plugin defers JSX compilation to Vantris/Rolldown and does nothing else.

The full option table, the "with options" wrapper pattern, and how each hook works are on the @vantris/react page.

Using HMR in a React app

With @vantris/react installed, individual components refresh automatically — you rarely touch import.meta.hot directly. When you need custom teardown for non-component modules (stores, singletons), the HMR API is still available and behaves the same as in any Vantris app.

Type declarations

Install the React types for editor support and your typecheck script:

bash
npm install -D @types/react @types/react-dom

And add vantris/client to tsconfig.json types for import.meta.env, import.meta.hot, and asset imports. See TypeScript and Client Types.

A complete minimal example

jsonc
// package.json
{
  "type": "module",
  "scripts": {
    "dev": "vantris dev",
    "build": "vantris build",
    "preview": "vantris preview"
  },
  "dependencies": { "react": "^19", "react-dom": "^19" },
  "devDependencies": {
    "vantris": "^1.2.0",
    "@vantris/react": "^0.1.0",
    "@types/react": "^19",
    "@types/react-dom": "^19"
  }
}
ts
// vantris.config.ts
import { defineConfig } from "vantris";
export default defineConfig({ plugins: ["@vantris/react"] });
tsx
// src/main.tsx
import { createRoot } from "react-dom/client";
import { App } from "./App";
createRoot(document.getElementById("app")!).render(<App />);
tsx
// src/App.tsx — edit this while the counter is non-zero; the count survives
import { useState } from "react";

export function App() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>Count: {n}</button>;
}

Released under the MIT License.