Octane is a fast JavaScript UI framework, and the successor to Inferno. You write components with the React API you already know, and a compiler turns them into direct DOM code before they ship. No virtual DOM, no rules-of-hooks bookkeeping, and no dependency arrays to maintain by hand.
Created by Dominic Gannaway, who also created Inferno and has worked on React, Lexical, Ripple, and Svelte.
import { useState } from 'octane';
export function Counter() @{
const [count, setCount] = useState(0);
<button onClick={() => setCount(count + 1)}>
{'Count: ' + count}
</button>
}
Why Octane
Your React knowledge transfers. useState, useEffect, memo, context,
portals, Suspense, transitions: same API, same mental model, checked case by case
against a large behavioral suite. React-derived coverage is tracked in the
generated parity report rather than inferred
from the size of the suite.
Standard JSX works, .tsrx gives you more. Paste a component from the React
docs into a .tsx file and it runs. Or author in .tsrx, the spiritual
successor to JSX, and get template directives (@if, @for, @switch, @try)
that compile to keyed fast paths, plus an @{ … } shorthand that puts setup next
to the output. Mix both dialects in one app and import across the boundary.
TSRX Syntax for VS Code
adds syntax highlighting, diagnostics, navigation, and completions for .tsrx
files.
Write the closure, not its dependency list. Omit the array from useEffect,
useMemo, useCallback, and friends, and the compiler derives it from what the
closure actually captures, including stable setters, dispatchers, refs, and state
getters. This is the no-bookkeeping DX people associate with signal frameworks,
without leaving the hooks model. Explicit arrays still mean exactly what they
mean in React.
No rules of hooks. Hooks are tracked by call site, not call order, so a hook
can live inside an if or after an early return. The one rule left is enforced
for you: a hook in a plain JS loop is a compile error, because every iteration
would share a single call-site slot. Use the keyed @for directive instead,
where each item gets its own hook state.
The platform, not a reimplementation of it. Real delegated DOM events,
controlled form components on native events (React's value/checked semantics,
with onInput per edit and native onChange on commit), and refs as plain props
(ref={cb}, ref={obj}, even ref={[a, b]}). No synthetic layer second-guessing
the browser.
No virtual DOM. Components re-render like React, but a compiled render path and an LIS-based keyed reconciler keep the runtime overhead minimal.
Octane's native renderer is deliberately narrow where React has grown wide: no class components, no Server Components, no synthetic event system. Those are choices, not gaps, and they are written down in Differences from React.
Also in the box
- Editable state that follows its source.
useLinkedStateresets or adjusts
- Promises in render are safe. No
cache()wrapper: creations feeding
use() are memoized at their declarations, including local .then chains.
Independent requests start together, one suspension per stratum, and descendant
fetch trees prefetch while an ancestor is still suspended.
- Streaming SSR and byte-stable hydration, with out-of-order Suspense
- Deferred hydration.
keeps server HTML visible but inert until
- Behavior-only roots for externally owned DOM. Attach abortable behavior
- React interoperability in both directions. Keep real React components
ReactCompat, or add Octane components to a React app with
OctaneCompat. Both are available from octane/react.
- Optional immutable render snapshots. Add
"use strong"to one module, or
class/classNamecomposes clsx-style everywhere: strings, arrays,
blocks are
sibling-scoped (a block styles its siblings and everything below them, never
its parent), and const theme = exposes $class plus one key
per class for class={theme.dark} and .
- A current-state getter.
useStateanduseReducerreturn
[state, update, getState], so a delayed callback can read the latest value
instead of a stale capture.
Install
Octane's published packages need Node.js 22.22.2 or newer.
Scaffold a project that already runs:
npm create octane my-app
cd my-app
npm run dev
--template spa is a client-only app and --template fullstack adds routing,
streaming SSR, hydration, and a production build; leave the flag off and it
asks.
In a project you already have, let the CLI wire it up instead, including the
TypeScript settings .tsrx needs:
pnpm dlx @octanejs/cli init
Or do it by hand:
pnpm add octane @octanejs/vite-plugin
// vite.config.ts
import { defineConfig } from 'vite';
import { octane } from '@octanejs/vite-plugin';
export default defineConfig({
plugins: [octane()],
});
// main.ts
import { createRoot } from 'octane';
import { App } from './App.tsrx';
const root = createRoot(document.getElementById('root')!);
root.render(App, { title: 'Hello world!' });
Rspack and Rsbuild are supported too. Getting started covers all three build tools, server rendering, hydration, streaming, deferred hydration, and profiling.
React interoperability
octane/react lets each renderer keep ownership of its own components:
| Boundary | What it renders | React version |
| -------------- | --------------------------------------------- | --------------------------------------------------------- |
| ReactCompat | Real React components inside an Octane app | Matching React and React DOM 19.2+ in the React 19 series |
| OctaneCompat | Compiled Octane components inside a React app | React 19 |
For a React component in an Octane template:
// App.tsrx — compiled by Octane.
import { ReactCompat } from 'octane/react';
import { Counter } from './Counter.react';
export function App() @{
<ReactCompat><Counter start={3} /></ReactCompat>
}
/* @jsxImportSource react /
// Counter.react.tsx — compiled by React's JSX transform.
import { useState } from 'react';
export function Counter({ start }: { start: number }) {
const [count, setCount] = useState(start);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Keep native Octane components in .tsrx and React components under React's JSX
transform. Mixed builds use requireDirective: true with both compilers; do not
alias React to Octane. The React interoperability guide
includes compiler setup and the component/props form.
ReactCompat preserves React state, events, and refs. Map native context into
React explicitly with bridgeReactContext(OctaneContext, ReactContext) and the
boundary's contexts prop. In the opposite direction, an Octane component
inside OctaneCompat can read a real React context with Octane's use or
useContext.
Both boundaries have server implementations in octane/react/server. Octane's
server compiler retargets the import automatically; React-owned server entries
that bypass it must select the server entry explicitly. ReactCompat starts or
updates React work after Octane commits, so Octane transitions and flushSync()
do not synchronously commit the React root. See the guide for pending updates,
SSR buffering, hydration, and nesting limits.
Status
Octane is in beta. The runtime, compiler, and SSR/hydration paths all work, but APIs still move.
The core suite contains 3,900+ distinct behavioral tests across conformance,
differential, hydration, runtime, compiler, and SSR coverage. The octane-prod
project reruns the normal suite against the production compiler path, which is
valuable mode coverage but is not counted again as unique tests. This is an
Octane suite count, not a claim that every test was ported from React; the pinned
snapshot and source-attributed React counts live in the
coverage ledger and report.
Documentation
The full docs live at octanejs.dev, a site built with Octane itself. Good places to start:
- Quick start: install, mount, and the
.tsrx essentials.
- Signals: stable scoped state, derived values,
- Build tools: Vite, Rspack, or Rsbuild
- TSRX vs TSX/JSX: when to reach for
- Publishing libraries: package
- Bindings: the
@octanejs/*ports of the
ReactCompat for React inside Octane, or OctaneCompat for Octane inside React.
In this repository:
- Getting started: install, build tools, mount, SSR,
- TSRX basics: components, hooks, control flow, class
- Signals: ownership, async queries, retained values, and hydration.
- Server rendering and
- Differences from React: the divergence
- ReactCompat: React inside Octane, including compiler
OctaneCompat direction.
- Bindings status: what each
@octanejs/*package
Packages
This is a pnpm monorepo. docs/packages.md is the
generated inventory; the shape of it is:
octaneis the runtime and the compiler together:
octane/compiler with bundler adapters at
octane/compiler/vite and octane/compiler/bundler. Custom Node build pipelines
can opt into type-aware text compilation.
- The app layer:
@octanejs/app-coreholds the
adapter-vercel and
adapter-cloudflare deploy the output;
@octanejs/tanstack-start is the TanStack Start
integration.
- Tooling:
@octanejs/cli(create,init,doctor,
analyze, add, explain, mcp add),
create-octane, the npm create octane entry
point onto octane create, and
@octanejs/mcp-server, which exposes Octane
docs and compile tooling to AI agents over MCP.
- The
@octanejs/*bindings, each an Octane port of a React library: state
Parity varies by package. Some are behaviorally complete, others are explicitly
partial or alpha, and
docs/bindings-status.md is the generated table of
record: upstream version, supported surface, known divergences, SSR/hydration
coverage, and when the evidence was last checked.
Sponsors
BlackSmith - fast and efficient platform for running GitHub Actions, helping teams build, test, and deploy code faster while reducing CI costs. We thank Blacksmith for supporting our community as a sponsor!
Contributing
Bug reports, regression tests, docs, bindings, and core fixes are all welcome. CONTRIBUTING.md covers setup, where a change belongs, the test policy, the generated files, and how pull requests are labelled and landed.
pnpm install
pnpm test
pnpm typecheck
License
MIT