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.

Get startedHow it works← these buttons are a component library written in the dialect
This button is an island: Go rendered it, and after the browser hydrates it, it's a signal. Click it.
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.

FAQ

Up and running in five minutes