Qwik
Resumable JS — tiny preloader, lazy chunks on interaction. Closest mental model to Resuma.
Ship HTML. Resume interactivity — never rehydrate.
Build your whole app in Rust — UI, server functions, and forms on web standards. Components run once on the server; signals drive targeted DOM updates without re-rendering the tree. 901 B loader, progressive enhancement, no WASM hydration.
Install: cargo install resuma · one crate for core + Flow + CLI
#[component]
fn Counter() -> View {
let count = use_signal(0);
view! {
<button onClick={
move |_| count.update(|c| *c += 1)
}>
"Count: " {count}
</button>
}
}
// Handler lazy-loads from /_resuma/handler/Counter.jsNo hydration. rs2js translates the closure; the runtime resumes signal state on first click.
Try it
Like Leptos server functions: Rust guaranteed to run on the server, callable from the browser. Errors surface in the UI without refreshing.
Rust runs on the server only. Click reload — no page refresh. Every 3rd call returns an error (check your server logs).
Positioning
Three ways to ship interactive UI after the first paint — Rust-native resumability compared to Qwik and WASM hydration.
Resumable JS — tiny preloader, lazy chunks on interaction. Closest mental model to Resuma.
Rust SSR + WASM hydration and optional islands.
Rust SSR + resumability + lazy JS handlers — no WASM by default.
Measured
Same UX across frameworks: SSR heading + one increment button. Gzip transfer sizes from production build artifacts (May 2026).
| Framework | Initial load | First interaction | Static page |
|---|---|---|---|
| Resuma | 901 B | 4.20 KiB | 0 B |
| Leptos | 79.02 KiB | 79.02 KiB | — |
| Next.js | 142.43 KiB | 142.43 KiB | — |
| React (Vite) | 57.99 KiB | 57.99 KiB | — |
| Astro | 57.76 KiB | 57.76 KiB | — |
| SvelteKit | 27.71 KiB | 27.71 KiB | — |
| Qwik | 1.96 KiB | 22.32 KiB | — |
| SolidStart | 16.75 KiB | 16.75 KiB | — |
| templ + HTMX | 16.21 KiB | 16.21 KiB | — |
Hydration frameworks load the same JS on page load — initial and first click match. Next.js uses the default create-next-app scaffold (~142 KiB). Resuma static pages ship 0 B client JS. Methodology & external validation · node benchmark/run.mjs
Performance model
Resumability means the client never re-runs your component tree. State and handlers are already in the HTML — the tiny runtime wires them up lazily.
#[server] RPC, #[submit] forms, and #[load] data — axum-native, no adapter boilerplate.
<Form submit> works as plain HTML POST before JS loads; runtime enhances in place.
Signals update bound DOM nodes only — no full component re-render on the client.
Every #[component] is a lazy boundary. Handlers externalise to /_resuma/handler/{Component}.js.
Under the hood
One SSR pass. One resumability payload. Lazy execution on the client.
Rust walks the View tree, emits HTML + data-r-on:* attributes, and serialises signals into <script type="resuma/state">.
Handler sources move to lazy chunks. computed! / effect! / debounce! replay on the client via rs2js.
Loader (901 B gzip) bootstraps signals. Core loads on first interaction. Handlers fetch on demand — or prefetch in viewport.
Components
Use view! with JSX-like syntax, fine-grained signals, and onClick handlers that compile to lazy JavaScript. No WASM bundle. No client-side component re-execution.
#[component]
fn SearchBar() -> View {
let q = use_signal(String::new());
let len = computed!([q], move || q.get().len());
view! {
<input
value={q}
onInput={move |e| q.set(e.value)}
placeholder="Filter…"
/>
<p>{format!("{} chars", len.get())}</p>
}
}Server actions
#[server] registers JSON-RPC at /_resuma/action/:name. Invoke from translated handlers or js!{} — CSRF-protected, typed, no manual API wiring.
#[server]
async fn search(q: String) -> Vec<String> {
db::search(&q).await
}
#[component]
fn LiveSearch() -> View {
let query = use_signal(String::new());
view! {
<input onInput={ js! {
state.query.set(event.target.value);
const r = await __resuma.action(
'search', [event.target.value]
);
state.results.set(r);
}} />
}
}Why Resuma?
Resumable SSR in Rust — one install, progressive enhancement, full-stack Flow when you need it.
File-based pages, #[load], #[submit], layouts, middleware — built into the same crate.
resuma build --static scaffolds HTML from src/pages/ for edge-friendly deploys.
resuma dev with HMR WebSocket, resuma new templates (basic, todo, flow).
view! translates Rust closures via rs2js. js!{} for escape hatches when you need raw client code.
#[island(load = "visible")] for heavy widgets — most UI only needs #[component].
Crypto CSRF, security headers, rate limits — see examples/todo for production patterns.
One package
Two layers, one dependency. Core stays stable; Flow adds routing, data loading, and forms.
RESUMA¹ — CORE
FLOW² — FULL-STACK
Integrations
Integration guides for SQLx, Turso, auth, validation, i18n, and E2E testing.