Architecture
How Resuma turns Rust components into instantly-interactive HTML without hydration.
The resumability promise
Traditional SSR: render on server → hydrate on client (re-run all components). Resuma: render once → serialize state → client resumes only what the user touches.
Server (Rust) ──HTML + payload──► Browser (~3KB)
render components parse resuma/state
serialize signals delegate events
lazy-import handlersPipeline of one click
- view! expansion — closure → rs2js (in resuma-macros) → HandlerRef in HTML
- SSR — walk View tree, emit data-r-on:* attributes + JSON payload
- Runtime — document listener, lazy fetch handler chunk, update signals
Payload format
<script type="resuma/state" id="resuma-state">
{"signals":[...],"handlers":{},"lazy_chunks":["Counter"],"islands":[],"actions":[]}
</script>
<script type="module" src="/_resuma/loader.js"></script>Component boundaries
Each #[component] emits <resuma-boundary data-r-chunk=\"Counter\"> for viewport prefetch. Handler JS loads from /_resuma/handler/Counter.js — not inlined in the payload.
Crates
| Crate / module | Role |
|---|---|
resuma | Single runtime crate — depend on this only |
resuma::core | Signals, View, resumability primitives |
resuma::ssr | HTML rendering + streaming chunks |
resuma::server | axum HTTP, /_resuma/* endpoints |
resuma::flow | FlowApp, pages, loads, submits |
resuma::router | File-based page scanner |
resuma-macros | view!, #[component], #[load], #[submit] (proc-macros) |
Resumability vs hydration
| Aspect | Classic SSR + hydration | Resuma |
|---|---|---|
| Client after load | Re-run components | Resume handlers only |
| Initial JS | App bundle grows with UI | ~3KB runtime + lazy chunks |
| Static pages | Often still ship framework JS | Zero client JS |
HTTP endpoints
GET /_resuma/runtime.js— client bootstrapPOST /_resuma/action/:name— #[server] RPCPOST /_resuma/submit/:name— #[submit] formsGET /_resuma/handler/:chunk— lazy handler JS