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
CategorySyntaxNotesStatus
Modulesimport X from "./a"default import; extensions .tsx / .server.tsx / .client.tsx may be omittedSupported
Modulesimport { a, b as c } from "./a"named importsSupported
Modulesimport type { T } from "./a"type import; client code may only import type from host:* (except actions)Supported
Modulesimport { useState, useEffect, useMemo } from "gotsx"hooksSupported
Modulesimport { createStore, seed } from "gotsx"stores: state shared by islands, seeded per request by the page (see the Hooks rows)Supported
Modulesimport type { Node, PageProps, LayoutProps, Meta, Flash } from "gotsx"framework typesSupported
Modulesimport { x } from "host:name"host module in a server component: compiles to a direct Go callSupported
Modulesimport { 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 / .fieldsSupported
Modulesexport function meta(props?: PageProps): Metapage metadata (title, description, canonical, image, noIndex), evaluated once per request and handed to every layout as props.metaSupported
Modulesprops.session / props.flash / props.csrfPageProps: signed-session values (read-only), one-shot flash messages, the CSRF token for classic <form method="post">Supported
Modulesexport default function / export functioncomponent (capitalized) or plain functionSupported
Modulesexport const data: T[] = [...]module-level const → Go package var; no letSupported
Modulesexport interface / export typetype export (all types can be import type'd)Supported
Modulespages/a/[id].server.tsx / pages/docs/[...slug].server.tsxfile routing: params.id; catch-all params.slug = "x/y/z"; more specific routes winSupported
Modulespages/**/_layout.server.tsx / _404 / _errornested layouts (LayoutProps = PageProps + meta + children, outer layouts wrap inner ones); _404 → gen.NotFound, _error (ErrorProps) → gen.ErrorPageSupported
Modulesimport { Suspense } from "gotsx"streaming boundary (server only): <Suspense fallback={…}> ships the fallback with the shell, children render in their own goroutine and stream inSupported
Modulesimport * as ns fromnamespace importUnsupported
Statementsconst x = ... / let x = ...single declaration; type annotated or inferredSupported
Statementsconst { a, b = 1 } = obj / const [x, setX] = ...destructuring + defaults (primitives, zero-value semantics)Supported
Statementsif / else if / elseJS truthiness (empty string, 0 are falsy)Supported
Statementsfor (const x of xs)for-of over arraysSupported
Statementsfor (let i = 0; i < n; i++) / while (cond) / break / continueclassic loops; the Go side uses a real for statement (continue runs the update)Supported
Statementsswitch (x) { case a: case b: … break; default: … }JS fall-through semantics are preserved (translated to Go fallthrough); switch (true) worksSupported
Statementsreturn / throwthrow is a panic on the Go side, recovered by the request layerSupported
Statementstry / catch / finallyfull on the client; Go runs only the try and finally bodiesSupported
Statementsfunction f() {} / async function f() {}nested functions; async only reaches the JS backendSupported
Statementsdo … while / for … in / labeled breakrewrite as while, or for-of over Object.keys(obj)Unsupported
Statementsconst a = 1, b = 2multiple declarations in one statementUnsupported
Expressionsnumber / string / template string / true / null / undefinednumber is float64Supported
Expressions[a, ...b] / { a, b: 1, ...c }array & object literals (object spread client-only)Supported
Expressionsa.b / a?.b / a[i] / a?.[i]member, optional chaining, indexSupported
Expressionsf(x) / f?.(x) / useState<T>(x)call, optional call, explicit type argumentSupported
Expressions! - + typeofunarySupported
Expressions+ - * / % === !== < > <= >=arithmetic, strict equality, comparisonSupported
Expressions&& || ??logical; for primitives ?? ≡ || (zero value = absent); an absent object (find miss, optional field) is falsy and === undefinedSupported
Expressionsa ? b : cternary (node or value)Supported
Expressions(a, b) => expr / x => { ... } / async () => ...arrow functions; param types inferred from contextSupported
Expressionsx = v / += -= *= /= %= / x++ x-- ++x --xassignment to variables, fields, Record keys and array indexes; ++/-- on numbersSupported
Expressionsx as T / x!type assertion, non-null assertionSupported
Expressionsawait xclient code onlySupported
Expressions== !=only === / !== allowedUnsupported
Expressionsnew / class / this / function expressionno classes or prototypesUnsupported
Expressions/pattern/gimsuregex literal, RE2 subset checked at compile time (no lookaround / backreferences); re.test, s.match/replace/replaceAll/split/searchSupported
Expressionsdelete 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 presenceSupported
Expressionsin / instanceof / voidUnsupported
JSX<div class="x" id={v} disabled>…</div> / <br />elements, string/expression/boolean-shorthand attrs, self-closingSupported
JSX<></>fragmentSupported
JSXonClick={fn} / onInput={(e) => ...}events; handler param type any; not generated on the serverSupported
JSXaria-* / data-* / rolebooleans render as "true" / "false"Supported
JSX{cond && <x/>} / {a ? <x/> : <y/>} / {list.map(...)}conditions & lists; reactive only if a signal is readSupported
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 rebuildsSupported
JSX<Comp prop={v}>children</Comp>component call; children is a NodeSupported
JSX{/* comment */}Supported
JSX<div {...props} />attribute spreadUnsupported
JSXpassing children to an islandisland props go through JSON in an HTML attribute — no room for a Node; children of shared & server components are fineUnsupported
JSXdangerouslySetInnerHTMLno raw-HTML injection hole; render tokens returned by a host module yourselfUnsupported
Typesstring number boolean void any undefined nullSupported
TypesT[] / Array<T> / Record<string, T>arrays, mapsSupported
Types{ a: string; b?: number; f(x: T): R }object types; optional primitive = zero-value semanticsSupported
Typesinterface A extends B, C { … } / type aliasextends copies the base fields (a same-named field overrides)Supported
Types"a" | "b" / T | undefinedliteral union → string; optionalSupported
Types(x: T) => R / Promise<T>function type; Promise<T> treated as TSupported
Typescustom generics / tuple / intersection / keyof / typeof / enum / classUnsupported
Hooksconst [x, setX] = useState(init)server = initial value; client = signalSupported
HookssetX(v) / setX(prev => ...)Supported
Hooksconst y = x * 2a signal-dependent const is automatically a memo, no useMemo neededSupported
HooksuseMemo(() => ...) / useEffect(() => ...)useEffect is client-only, deps tracked automaticallySupported
Hooksexport 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
Hookscart.count / const { count, items } = cartreads are signals (fine-grained bindings, memos, effects); client modules only; read-only outside setSupported
Hookscart.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 stateSupported
Hooksseed(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
HooksuseRef / useContext / useReducerUnsupported
Builtinsconsole.log / JSON.stringify / Math.max min floor ceil round abs sqrt randomon both sidesSupported
BuiltinsString() Number() Boolean() parseInt parseFloat isNaN encodeURIComponentSupported
Builtinsfetch setTimeout document window location history navigator localStorageclient only, type anySupported
BuiltinsObject.keys / Object.valueskeys sorted (matching a Go map, keeping hydration stable)Supported
BuiltinsMath.pow sign truncSupported
Builtinsredirect(url, status?) / notFound()server pages only: abort the render and answer with a 3xx / the 404 page (return redirect(…) also works)Supported
BuiltinsDate.now() / Date.parse(iso) / isoDate(ms)milliseconds on both sides; format with fmtDate / isoDateSupported
BuiltinsArray.from / new Date(...) / Object.assignno constructors; dates are numbers + isoDateUnsupported
Arrayslength map filter find findIndex some every includes indexOf lastIndexOf join slice concat forEachfind on a miss yields the zero value, which is falsy and === undefinedSupported
Arrayssort reduce reverse flat atsort/reduce match the Go backend (copy, don't mutate)Supported
Arrayspush pop shift unshift splicein place, on a variable / field / index (the Go side takes its address); not on a useState array — use setXs([...xs, x])Supported
Stringslength toUpperCase toLowerCase trim includes startsWith endsWith split slice replace replaceAll repeat indexOf charAtGo side works by runeSupported
StringspadStart padEnd trimStart trimEnd lastIndexOf at localeCompare toString / number toFixed toStringby rune; localeCompare compares by code point on both sides (not locale-aware)Supported
StringsmatchAll / named groups / sticky regexnot in the RE2 subsetUnsupported

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 to n().
  • 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 import host:*, never reaches the browser.
  • *.client.tsx: compiled to Go (single-pass SSR) and JS; may only import type host 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.