v0.6No NodeNo JS engineNo npm
React's ideas,
compiled to Go
One TSX source, two compilers: the server side becomes Go functions, the client side becomes signals. No vdom, no JS engine, no Node. The toolchain is just Go.
15 µs
50-item list page rendered in Go (in-process)
20k req/s
4-vCPU GitHub runner, 64 connections — same as templ, ~65× Next.js
21 MB
peak memory under load (Next.js: 387 MB)
9 KB
first-load JS, gzipped (runtime + loader + islands)
One source, two compilers
.server.tsx compiles only to Go. .client.tsx compiles to both Go (single-pass evaluation for SSR) and JS (signals for interactivity). Switch tabs to see what one source becomes.
Server page → Go
import type { PageProps } from "gotsx";
import { models } from "host:data"; // Go-backed, zero marshalling
import Layout from "../components/Layout.server";
import ModelCard from "../components/ModelCard.server";
export default function Home({ query }: PageProps) {
const q = query.q ?? "";
const list = models.search(q); // synchronous: concurrency from goroutines
return (
<Layout title="Products">
<div class="grid">{list.map((m) => <ModelCard model={m} />)}</div>
{list.length === 0 && <p class="empty">No matching products</p>}
</Layout>
);
}Island → Go + JS
import { useState, useEffect } from "gotsx";
export default function Counter({ start }: { start: number }) {
const [n, setN] = useState(start);
const double = n * 2; // depends on n → auto memo
useEffect(() => { console.log(n); }); // JS backend only
return (
<button onClick={() => setN(n + 1)}>
{n} ×2 = {double}{n > 4 && <b> 🔥</b>}
</button>
);
}Why TSX can compile to Go
SSR is a single synchronous one-pass evaluation: no re-render, no effects, setters are never called. So the server needs no "React runtime", only the component's "render slice" — and the semantics of that slice are small enough to compile to Go.Features
Compiled to Go, not interpreted
Server components are real Go functions: JSX becomes direct string writes, useState is the initial value, useEffect generates nothing. A page renders in tens of microseconds.
signals on the client
The same .client.tsx compiles to signals + fine-grained DOM binding (the Solid / Svelte 5 model), no vdom, a 6KB runtime.
Resumable hydration
The server only marks the reactive text/conditions/lists; the client claims nodes in the same structural order the same compiler produced, no diffing, reusing existing DOM.
Host modules
import { models } from "host:data" is backed by Go; after compilation it's a direct call, zero marshalling; types are reflected from Go.
Islands + SPA navigation
Pages ship zero JS; islands load on demand; navigation = fetch HTML → morph, islands survive by DOM identity so state isn't lost.
The fence is a compile error
The client touching host:*, a missing prop, member access on any, using ==… are all errors with file:line:col, never a silent miscompile.
Tailwind
class is just a string; the Tailwind standalone CLI scans .tsx at build time to generate CSS, also without Node. This site is proof.
gotsx dev + LSP
Edit TSX → recompile → go build → restart in ~2s and the browser reloads itself; a failing compile keeps the old version serving. gotsx lsp brings the same errors into VS Code / Neovim / Helix / Zed as you type.
What it is not
Not React
No React runtime, and you can't use React component libraries from npm. It borrows the ideas and the syntax feel; the semantics are static.
Not TypeScript
It's a static language that borrows TSX syntax (a cousin of AssemblyScript): the type system is bounded by what Go can represent, and stepping outside the subset is a compile error.
Not Next
No RSC protocol, no server actions, no client-side data fetching framework. Streaming is a <Suspense> boundary resolved in a goroutine; what you get is Go's speed, a single binary, and a 6KB client.