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 handlers

Pipeline of one click

  1. view! expansion — closure → rs2js (in resuma-macros) → HandlerRef in HTML
  2. SSR — walk View tree, emit data-r-on:* attributes + JSON payload
  3. 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 / moduleRole
resumaSingle runtime crate — depend on this only
resuma::coreSignals, View, resumability primitives
resuma::ssrHTML rendering + streaming chunks
resuma::serveraxum HTTP, /_resuma/* endpoints
resuma::flowFlowApp, pages, loads, submits
resuma::routerFile-based page scanner
resuma-macrosview!, #[component], #[load], #[submit] (proc-macros)

Resumability vs hydration

AspectClassic SSR + hydrationResuma
Client after loadRe-run componentsResume handlers only
Initial JSApp bundle grows with UI~3KB runtime + lazy chunks
Static pagesOften still ship framework JSZero client JS

HTTP endpoints

  • GET /_resuma/runtime.js — client bootstrap
  • POST /_resuma/action/:name — #[server] RPC
  • POST /_resuma/submit/:name — #[submit] forms
  • GET /_resuma/handler/:chunk — lazy handler JS