Author here. The novel piece is the Pages compiler (Manouche — named after the Django Reinhardt jazz genre): page!, head!, and form! macros go through TokenStream → AST → validation → IR → codegen and emit both client WASM and server SSR code from the same source. A #[server_fn] is callable from client components but compiles to a server-only function with full DI access:<p><pre><code> use reinhardt::DatabaseConnection; use reinhardt::db::orm::Model; use reinhardt::pages::server_fn::{ServerFnError, server_fn}; #[server_fn] async fn list_active_users(#[inject] db: DatabaseConnection) -> Result<Vec<User>, ServerFnError> { User::objects() .filter_by(User::field_is_active().eq(true)) .all_with_db(&db) .await .map_err(|e| ServerFnError::application(format!("DB error: {e}"))) } </code></pre> The same file holds the client component that calls it — list_active_users is invoked as an ordinary async Rust function; on WASM the macro rewrites it into a typed RPC call:<p><pre><code> use reinhardt::pages::component::Page; use reinhardt::pages::page; use reinhardt::pages::reactive::hooks::{Action, use_action}; pub fn active_users_view() -> Page { // use_action works uniformly on native (SSR) and WASM; on native the future // is dropped after a synchronous Idle→Pending→Idle cycle, so SSR renders the // empty shell that WASM later hydrates and populates. let load = use_action(|_: ()| async move { list_active_users().await.map_err(|e| e.to_string()) }); load.dispatch(()); page!(|load: Action<Vec<User>, String>| { div { watch { if load.is_pending() { p { "Loading..." } } else if let Some(err) = load.error() { p { { err } } } else { ul { { Page::Fragment( load.result().unwrap_or_default().iter() .map(|u| page!(|name: String| li { { name } })(u.username.clone())) .collect::<Vec<_>>() ) } } } } } })(load) } </code></pre> No OpenAPI schema, no hand-rolled fetch, no duplicated request/response types between client and server. The #[server_fn] macro generates the RPC endpoint + JSON codec on the server, a typed async stub on the client, and hydration markers so SSR-rendered HTML stays consistent after WASM takes over.<p>Website: <a href="https://reinhardt-web.dev" rel="nofollow">https://reinhardt-web.dev</a><p>docs.rs: <a href="https://docs.rs/crate/reinhardt-web/latest" rel="nofollow">https://docs.rs/crate/reinhardt-web/latest</a>
by kent8192
|
Apr 22, 2026, 2:47:17 PM