Language reference
The gotsx dialect is a static language that borrows TSX syntax, not an implementation of a TypeScript subset — it has no React runtime, and its type system is bounded by what Go can represent. The subset is defined by the type system: if every expression has a static type that falls inside the allowed set, it compiles; otherwise it's a compile error with a location.
A few semantic conventions
number is float64 · an optional primitive represents absence with its zero value ("" / 0 / false) · strings are handled by rune on the Go side · || and ?? treat an empty string as falsy · code running on the server may not contain DOM / async / Node, code running only in the browser may.Syntax table
73 supported, 13 explicitly unsupported (they error). This table is itself an island: the filtering happens in the browser.
86 / 86
| Category | Syntax | Notes | Status |
|---|---|---|---|
| Modules | import X from "./a" | default import; extensions .tsx / .server.tsx / .client.tsx may be omitted | Supported |
| Modules | import { a, b as c } from "./a" | named imports | Supported |
| Modules | import type { T } from "./a" | type import; client code may only import type from host:* (except actions) | Supported |
| Modules | import { useState, useEffect, useMemo } from "gotsx" | hooks | Supported |
| Modules | import { createStore, seed } from "gotsx" | stores: state shared by islands, seeded per request by the page (see the Hooks rows) | Supported |
| Modules | import type { Node, PageProps, LayoutProps, Meta, Flash } from "gotsx" | framework types | Supported |
| Modules | import { x } from "host:name" | host module in a server component: compiles to a direct Go call | Supported |
| Modules | import { toggle } from "host:name" (island) → await toggle(id) | typed action: a Go method listed in Registry[...].Actions; the call is a same-origin POST typed Promise<T> from the Go signature; errors throw with .status / .fields | Supported |
| Modules | export function meta(props?: PageProps): Meta | page metadata (title, description, canonical, image, noIndex), evaluated once per request and handed to every layout as props.meta | Supported |
| Modules | props.session / props.flash / props.csrf | PageProps: signed-session values (read-only), one-shot flash messages, the CSRF token for classic <form method="post"> | Supported |
| Modules | export default function / export function | component (capitalized) or plain function | Supported |
| Modules | export const data: T[] = [...] | module-level const → Go package var; no let | Supported |
| Modules | export interface / export type | type export (all types can be import type'd) | Supported |
| Modules | pages/a/[id].server.tsx / pages/docs/[...slug].server.tsx | file routing: params.id; catch-all params.slug = "x/y/z"; more specific routes win | Supported |
| Modules | pages/**/_layout.server.tsx / _404 / _error | nested layouts (LayoutProps = PageProps + meta + children, outer layouts wrap inner ones); _404 → gen.NotFound, _error (ErrorProps) → gen.ErrorPage | Supported |
| Modules | import { Suspense } from "gotsx" | streaming boundary (server only): <Suspense fallback={…}> ships the fallback with the shell, children render in their own goroutine and stream in | Supported |
| Modules | import * as ns from | namespace import | Unsupported |
| Statements | const x = ... / let x = ... | single declaration; type annotated or inferred | Supported |
| Statements | const { a, b = 1 } = obj / const [x, setX] = ... | destructuring + defaults (primitives, zero-value semantics) | Supported |
| Statements | if / else if / else | JS truthiness (empty string, 0 are falsy) | Supported |
| Statements | for (const x of xs) | for-of over arrays | Supported |
| Statements | for (let i = 0; i < n; i++) / while (cond) / break / continue | classic loops; the Go side uses a real for statement (continue runs the update) | Supported |
| Statements | switch (x) { case a: case b: … break; default: … } | JS fall-through semantics are preserved (translated to Go fallthrough); switch (true) works | Supported |
| Statements | return / throw | throw is a panic on the Go side, recovered by the request layer | Supported |
| Statements | try / catch / finally | full on the client; Go runs only the try and finally bodies | Supported |
| Statements | function f() {} / async function f() {} | nested functions; async only reaches the JS backend | Supported |
| Statements | do … while / for … in / labeled break | rewrite as while, or for-of over Object.keys(obj) | Unsupported |
| Statements | const a = 1, b = 2 | multiple declarations in one statement | Unsupported |
| Expressions | number / string / template string / true / null / undefined | number is float64 | Supported |
| Expressions | [a, ...b] / { a, b: 1, ...c } | array & object literals (object spread client-only) | Supported |
| Expressions | a.b / a?.b / a[i] / a?.[i] | member, optional chaining, index | Supported |
| Expressions | f(x) / f?.(x) / useState<T>(x) | call, optional call, explicit type argument | Supported |
| Expressions | ! - + typeof | unary | Supported |
| Expressions | + - * / % === !== < > <= >= | arithmetic, strict equality, comparison | Supported |
| Expressions | && || ?? | logical; for primitives ?? ≡ || (zero value = absent); an absent object (find miss, optional field) is falsy and === undefined | Supported |
| Expressions | a ? b : c | ternary (node or value) | Supported |
| Expressions | (a, b) => expr / x => { ... } / async () => ... | arrow functions; param types inferred from context | Supported |
| Expressions | x = v / += -= *= /= %= / x++ x-- ++x --x | assignment to variables, fields, Record keys and array indexes; ++/-- on numbers | Supported |
| Expressions | x as T / x! | type assertion, non-null assertion | Supported |
| Expressions | await x | client code only | Supported |
| Expressions | == != | only === / !== allowed | Unsupported |
| Expressions | new / class / this / function expression | no classes or prototypes | Unsupported |
| Expressions | /pattern/gimsu | regex literal, RE2 subset checked at compile time (no lookaround / backreferences); re.test, s.match/replace/replaceAll/split/search | Supported |
| Expressions | delete m.key / delete m[key] / Object.hasOwn(m, key) | Record keys: reads of an absent key give the zero value on both sides; hasOwn tests presence | Supported |
| Expressions | in / instanceof / void | Unsupported | |
| JSX | <div class="x" id={v} disabled>…</div> / <br /> | elements, string/expression/boolean-shorthand attrs, self-closing | Supported |
| JSX | <></> | fragment | Supported |
| JSX | onClick={fn} / onInput={(e) => ...} | events; handler param type any; not generated on the server | Supported |
| JSX | aria-* / data-* / role | booleans render as "true" / "false" | Supported |
| JSX | {cond && <x/>} / {a ? <x/> : <y/>} / {list.map(...)} | conditions & lists; reactive only if a signal is read | Supported |
| JSX | {list.map((x) => <li key={x.id}>…</li>)} | keyed list: the client reuses / moves / disposes DOM per key, so inputs, focus and per-row effects survive reorders; without key the list rebuilds | Supported |
| JSX | <Comp prop={v}>children</Comp> | component call; children is a Node | Supported |
| JSX | {/* comment */} | Supported | |
| JSX | <div {...props} /> | attribute spread | Unsupported |
| JSX | passing children to an island | island props go through JSON in an HTML attribute — no room for a Node; children of shared & server components are fine | Unsupported |
| JSX | dangerouslySetInnerHTML | no raw-HTML injection hole; render tokens returned by a host module yourself | Unsupported |
| Types | string number boolean void any undefined null | Supported | |
| Types | T[] / Array<T> / Record<string, T> | arrays, maps | Supported |
| Types | { a: string; b?: number; f(x: T): R } | object types; optional primitive = zero-value semantics | Supported |
| Types | interface A extends B, C { … } / type alias | extends copies the base fields (a same-named field overrides) | Supported |
| Types | "a" | "b" / T | undefined | literal union → string; optional | Supported |
| Types | (x: T) => R / Promise<T> | function type; Promise<T> treated as T | Supported |
| Types | custom generics / tuple / intersection / keyof / typeof / enum / class | Unsupported | |
| Hooks | const [x, setX] = useState(init) | server = initial value; client = signal | Supported |
| Hooks | setX(v) / setX(prev => ...) | Supported | |
| Hooks | const y = x * 2 | a signal-dependent const is automatically a memo, no useMemo needed | Supported |
| Hooks | useMemo(() => ...) / useEffect(() => ...) | useEffect is client-only, deps tracked automatically | Supported |
| Hooks | export const cart = createStore<T>(init) | module-level const of a client module (app/stores/cart.client.tsx): a store — one signal per field in the browser, the request's seeded value on the server; fields missing from init are zero values; state is JSON (no functions, no Node) | Supported |
| Hooks | cart.count / const { count, items } = cart | reads are signals (fine-grained bindings, memos, effects); client modules only; read-only outside set | Supported |
| Hooks | cart.set((s) => { s.items.push(x); s.count += 1; }) / cart.set(value) | from handlers, effects and plain functions, never during render: s is a copy-on-write draft — only the fields that changed notify, untouched rows of a keyed list keep their identity; a whole value replaces the state | Supported |
| Hooks | seed(cart, value) | in the body of a page or layout's default component, before the JSX: the islands of this request render with value and the browser's store starts from it (no flash, no refetch) | Supported |
| Hooks | useRef / useContext / useReducer | Unsupported | |
| Builtins | console.log / JSON.stringify / Math.max min floor ceil round abs sqrt random | on both sides | Supported |
| Builtins | String() Number() Boolean() parseInt parseFloat isNaN encodeURIComponent | Supported | |
| Builtins | fetch setTimeout document window location history navigator localStorage | client only, type any | Supported |
| Builtins | Object.keys / Object.values | keys sorted (matching a Go map, keeping hydration stable) | Supported |
| Builtins | Math.pow sign trunc | Supported | |
| Builtins | redirect(url, status?) / notFound() | server pages only: abort the render and answer with a 3xx / the 404 page (return redirect(…) also works) | Supported |
| Builtins | Date.now() / Date.parse(iso) / isoDate(ms) | milliseconds on both sides; format with fmtDate / isoDate | Supported |
| Builtins | Array.from / new Date(...) / Object.assign | no constructors; dates are numbers + isoDate | Unsupported |
| Arrays | length map filter find findIndex some every includes indexOf lastIndexOf join slice concat forEach | find on a miss yields the zero value, which is falsy and === undefined | Supported |
| Arrays | sort reduce reverse flat at | sort/reduce match the Go backend (copy, don't mutate) | Supported |
| Arrays | push pop shift unshift splice | in place, on a variable / field / index (the Go side takes its address); not on a useState array — use setXs([...xs, x]) | Supported |
| Strings | length toUpperCase toLowerCase trim includes startsWith endsWith split slice replace replaceAll repeat indexOf charAt | Go side works by rune | Supported |
| Strings | padStart padEnd trimStart trimEnd lastIndexOf at localeCompare toString / number toFixed toString | by rune; localeCompare compares by code point on both sides (not locale-aware) | Supported |
| Strings | matchAll / named groups / sticky regex | not in the RE2 subset | Unsupported |
Reactivity rules
The client update model is decided by the compiler; there's no such thing as hooks call order
const [n, setN] = useState(0)— n is a signal; every place that reads n compiles ton().const double = n * 2— a const whose initializer reads a signal is automatically a memo.- JSX text, attributes, conditions, and lists that read a signal/memo each bind one effect; anything that doesn't is static and leaves no marker on the server either.
list.map(cb)reactivity depends only on list, not on the callback body; conditions and ternaries depend only on the condition.- Component props are not reactive (like Solid, a signal's value is read once at creation); put reactive content into children or a conditional block.
Server / client / shared
*.server.tsx: Go only, may importhost:*, never reaches the browser.*.client.tsx: compiled to Go (single-pass SSR) and JS; may onlyimport typehost types; the default export is an island.- No suffix: a shared component, compiled to both sides, may touch neither the host nor the DOM — a pure render function.
- The boundary is enforced by the compiler: a client importing a server component, or await appearing on the server, are both compile errors.