go-app Architecture Analysis and Evolution Roadmap (v11)
> Analysis target: github.com/maxence-charriere/go-app/v11 (PWA + WebAssembly framework)
> Dimensions: architecture, design philosophy, module collaboration, technical debt, evolution path
Key points
- go-app is a full-stack Go framework for PWAs: the same Go source performs SSR on the server and runs as WebAssembly in the browser, eliminating front-end/back-end code splitting.
- Core selling points: declarative UI with zero templates (all HTML expressed via Go function chains generated by
gen/html.go), single-binary deployment (one Go binary + one.wasm), and batteries-included PWA features (service worker, manifest, push, offline cache, auto-update). - The runtime model is isomorphic Go, driven by
IsClient/IsServerpredicates based onGOARCH == "wasm"; browser APIs have stub implementations for the server (js_nowasm.go) at the cost of a build-tag dual-track system. - engineX implements a React-like single-threaded event loop with 4096-capacity
dispatches/defersbuffered channels and adaptive frame ticking (idle = 1 hour; active = 1/120 s). - Components embed
app.Compoand optionally implement nine lifecycle interfaces (Init, Mount, Dismount, Nav, Update, PreRender, AppUpdate, AppInstallChange, Resize). - The diff algorithm is reflection-based comparison of type/tag/attributes/handlers — there is no key concept, which hurts dynamic list performance and correctness.
Contextis a compile-time type-safe DI container: a struct with 17 function fields (272 bytes copied by value per dispatch).pkg/cache/lru.go:97— LRU eviction sorts all priorities per eviction (O(n log n)); recommend doubly-linked list + map for O(1).pkg/errors/errors.go:253—errors.Error.Isusesreflect.DeepEqualon Tags; recommend shallow/hash comparison.pkg/app/node.go:37—FilterUIElemsdoes reflection on the render hot path; add anisZero()method to theUIinterface.pkg/app/update.go:16—updateManager.Addallocates 100 maps at once when depth ≥ 100.- Context copied by value (272 bytes, 17 func fields) on every dispatch; recommend
*Context. pkg/appis a god package (40+ files, nine responsibilities: UI engine, HTTP, routing, state, crypto, notifications, storage, testing). Recommend splitting intoapp/core,app/http,app/state,app/testing,app/pwawith compatibility aliases.- Global
routes/windowvariables inapp.gohurt test isolation; introduce anAppstruct with a default instance. Handleris a god object (30+ config fields, 11 sequential initializers); extractpwamanifest.Builder,sw.Builder,ResourceCache.logs.Encoderis a plain package-levelvar— a data race; useatomic.Value.- No backpressure monitoring on the 4096 dispatch/defer channels; add Drop/Block metrics hooks.
- Service worker
fetchWithCache(app-worker.js:52) is purely cache-first with no revalidation; adopt per-Content-Type strategies. - Router lacks 405/406 semantics; unmatched paths all fall through to
notFound{}. - Testing limited to TestEngine; no browser-like simulation — suggest chromedp integration templates.
- Errors serialize to single-line JSON, unfriendly in consoles; default to indented encoder in dev mode.
- No official scaffolding; a
go-app newcommand could be built cheaply on the existingclipackage. - No ADRs for key decisions (IsClient/IsServer, why-no-keys, why-panic); add
docs/adr/. - No built-in metrics/tracing (dispatch queue depth, update timing, mount counts); provide an optional
app.Metricsinterface.
Design philosophy
1. Go-first, JS-last (JS only for the SW template and glue). 2. Minimal interfaces, private structs; users receive interfaces, not structs. 3. Fail-fast via panic for framework-level invariants, recovered at the engineX top level — deviating from idiomatic error-return culture. 4. JSON-serializable cross-cutting concerns (errors, logs) for crossing the WASM/server boundary. 5. Self-hosting: the docs site itself is a go-app PWA (dogfooding).
Technical debt highlights
Performance
Architecture
Reliability
Developer experience, docs, observability
Comparison with peers
| Dimension | go-app | React + Vite | SvelteKit | Astro | |---|---|---|---|---| | Isomorphic language | Go ↔ Go (WASM) | JS ↔ JS | JS ↔ JS | JS ↔ JS | | Initial payload | Large (~2–3 MB WASM) | Medium | Small | Tiny (zero JS by default) | | Type safety | ★★★★★ | ★★★ (TS) | ★★★ (TS) | ★★★ (TS) | | Ecosystem | ★★ | ★★★★★ | ★★★★ | ★★★★ | | Built-in PWA | ★★★★★ | ★★ | ★★★ | ★★ | | Learning curve | Steep | Gentle | Gentle | Medium |
Its irreplaceability: when Go is the team's primary language and the same business model must run in browser and server, go-app is the option with zero language-switching cost.
Recommended roadmap
Phase 1 — internal decoupling (v11.x, backward compatible)
1. Split pkg/app internally behind a facade (appcore/apphttp/apptesting/apppwa).
2. Introduce an App instance; delegate legacy package-level functions to a defaultApp.
3. Rewrite LRU as list + map; benchmark for ~10× throughput.
4. Optimize errors.Is via Tags hash caching.
5. Make logs.Encoder atomic.
Phase 2 — developer experience (v12)
1. go-app new <project> scaffolding.
2. Introduce keys: Range(...).Slice(...).Key(func(i int) string) for dynamic lists.
3. Slim Context by grouping navigate/page/storage into a Browser sub-field.
4. Metrics interface exposing dispatch queue depth, update counts, mount/dismount events.
5. Optional chromedp-based integration test toolkit (pkg/apptest).
Phase 3 — modernization (v13+)
1. WASM size reduction via TinyGo/tree-shaking, targeting < 1 MB.
2. HTTP/3 and streaming SSR leveraging Go 1.22+ QUIC support.
3. Adaptive service worker strategies (cache-first / network-first / stale-while-revalidate).
4. AI-friendly godoc examples on all exported functions.
5. Expand an official widget library on pkg/ui (DataGrid, Chart, Form).
Conclusion
go-app is an opinionated niche framework trading Go's strong typing and isomorphic WASM for type safety and single-binary deployment, at the cost of a bloated package structure, reflection overhead, and a small ecosystem. Its biggest issue is not technical backwardness but "over-consistency" — reinventing cache, cli, errors, and logs dilutes iteration bandwidth for the core UI engine. The path forward is not chasing React but deepening existing strengths (isomorphic model, declarative UI, TestEngine) while dismantling the god package, adding observability, and reducing reflection. The three phases span roughly 18–24 months on an annual release cadence.