Programming Model
Vango is opinionated about where code belongs. If you follow the model, the framework stays predictable under refactors and deploys. If you fight it, you end up recreating the same ownership bugs Vango was designed to prevent.
The Core Rules
The server owns authoritative UI state.
The session loop is the single reactive writer.
vango.Setup allocates state and lifecycle.
Render closures stay pure.
Blocking reads belong in setup.Resource or setup.ResourceKeyed .
Blocking mutations belong in setup.Action or a page Action .
Dynamic lists use RangeKeyed with stable identity.
DOM ownership stays explicit: server-owned by default, hook/island/WASM only when chosen deliberately.
Setup Allocates, Render Computes
1func TodoList(p TodoListProps) vango.Component {
2 return vango.Setup(p, func(s vango.SetupCtx[TodoListProps]) vango.RenderFn {
3 items := setup.Signal(&s, []Todo{})
4 newText := setup.Signal(&s, "")
5
6 return func() *vango.VNode {
7 return Div(
8 Input(Value(newText.Get()), OnInput(newText.Set)),
9 RangeKeyed(items.Get(), func(item Todo) string { return item.ID }, renderTodo),
10 )
11 }
12 })
13}
This separation is the center of the framework:
Setup runs once per mount
render runs repeatedly
Setup can allocate
render must not allocate or do blocking work
Async Work Has A Home
| async read | setup.Resource / setup.ResourceKeyed |
|---|---|
| async live mutation | setup.Action |
| progressive form POST | page Action + setup.RouteForm |
| shareable URL state | setup.URLParam |
| browser behavior on server-owned DOM | Hook(...) |
| client-owned subtree | JSIsland(...) |
| client-local Go execution | WASMComponent(...) |
Stop sign
If you feel pressure to do blocking I/O in render, Setup, event handlers, `OnMount`, `Effect`, or `OnChange`, you are on the wrong side of the Vango contract.
Navigation Rules
Use Link(...) and NavLink(...) for normal navigation.
Use ctx.Navigate(...) from event handlers or post-commit lifecycle code.
Do not call ctx.Navigate(...) from a page handler or render closure.
If external callbacks need to write reactive state, dispatch back onto the session loop with ctx.Dispatch(...) instead of inventing another concurrency path.
Identity Matters
Dynamic lists should use RangeKeyed with a stable key. Keyed identity is not optional bookkeeping in Vango; it is part of patch correctness.
Read Further
Components & State
Routing & User Flows
Client Boundaries