Two rendering profiles, one core

Why Rustelo serves HTMX-SSR and Leptos hydration from the same configuration

Rustelo 4 min read
Hydrating every page costs a WebAssembly build, a large bundle and a class of bugs a content site never needed. Rustelo made the rendering strategy a typed choice per route instead of a property of the framework.

Two rendering profiles, one core

Rustelo started with one way to render a page: compile every page component to WebAssembly and hydrate it in the browser. For an interactive application that is the right trade. For a site made mostly of articles it is not. The cost lands where the site gets nothing back: a wasm32 build in every pipeline, bundles measured in megabytes, a toolchain that breaks on version mismatches, and hydration mismatches between the server markup and the client signals — a kind of bug that only exists because the page was hydrated at all.

The decision: a typed axis, not a fork

Instead of a second framework, Rustelo added a rendering profile to the one it had (ADR-006). RenderingProfile is an enum with two values:

  • LeptosHydration — Leptos server rendering plus WASM hydration. The default, so existing sites keep their behaviour without changing a line.
  • HtmxSsr — HTML rendered on the server, with HTMX for interaction: theme toggle, language switch, filters and forms go through dedicated endpoints with out-of-band swaps. No WebAssembly.

A route can declare its profile; a workspace declares a default; the enum supplies the last fallback. Resolution is route over workspace over default, and it happens during route resolution, so every resolved route already carries its effective profile.

Why typed matters

The profile is a type, not a string scattered across crates. Configuration that names an unknown profile is rejected when it is parsed, and code that dispatches on the profile matches an enum the compiler checks for exhaustiveness. The project keeps that as a hard constraint: the profile names appear as strings only where they are parsed.

What a site gains

A site whose routes are all HtmxSsr skips the wasm32 build entirely — one native target, a smaller artifact, no wasm-bindgen in CI. Its pages are templates rendered with Minijinja, and changing a template needs no Rust rebuild.

A site that needs both keeps both. A dashboard-style page that needs instant local feedback can stay on LeptosHydration while the articles around it are served as plain HTML from the same binary. The cost is honest: interaction under HTMX-SSR is a round trip to the server, which is right for content and wrong for a rich interface — which is exactly why the choice is per route.

Extension points instead of switches

Each profile reaches site-specific code through extension points the framework defines and the site fills once at startup: a shell renderer, fragments, filters, templates. Without a registration the framework answers with a typed error instead of a broken swap. The framework stays the same for every site; what changes is what each site registers.

This site is served by the HTMX-SSR profile.

· reads