31 releases
Uses new Rust 2024
| 0.1.0-rc.15 | Mar 29, 2026 |
|---|---|
| 0.1.0-rc.14 | Mar 24, 2026 |
| 0.1.0-rc.1 | Feb 28, 2026 |
| 0.1.0-alpha.4 | Jan 30, 2026 |
#507 in WebAssembly
1,369 downloads per month
Used in 9 crates
(6 directly)
3.5MB
69K
SLoC
reinhardt-pages
WASM-based reactive frontend framework for Reinhardt with Django-like API.
Features
- Fine-grained Reactivity: Leptos/Solid.js-style Signal system with automatic dependency tracking
- Hybrid Rendering: SSR + Client-side Hydration for optimal performance and SEO
- Django-like API: Familiar patterns for Reinhardt developers
- Low-level Only: Built on wasm-bindgen, web-sys, and js-sys (no high-level framework dependencies)
- Security First: Built-in CSRF protection, XSS prevention, and session management
- Simplified Conditional Compilation:
cfg_aliasesintegration and automatic event handler handling
Quick Start
Using the Prelude (Recommended)
The prelude provides all commonly used types with a single import:
// Instead of multiple scattered imports:
// use reinhardt_pages::{Signal, View, use_state, ...};
// use reinhardt_pages::component::{ElementView, IntoView};
// use reinhardt_pages::reactive::{Effect, Memo};
// Use the unified prelude:
use reinhardt_pages::prelude::*;
// or via reinhardt crate:
use reinhardt::pages::prelude::*;
Platform-Agnostic Event Type
The platform module provides unified types that work across both WASM and native:
use reinhardt_pages::platform::Event;
// Works on both WASM and native targets
fn handle_click(_event: Event) {
// Event handling logic
}
Simplified cfg Attributes with cfg_aliases
Configure cfg_aliases in your project's build.rs:
// build.rs
use cfg_aliases::cfg_aliases;
fn main() {
// Rust 2024 edition requires explicit check-cfg declarations
println!("cargo::rustc-check-cfg=cfg(wasm)");
println!("cargo::rustc-check-cfg=cfg(native)");
cfg_aliases! {
wasm: { target_arch = "wasm32" },
native: { not(target_arch = "wasm32") },
}
}
Add to Cargo.toml:
[build-dependencies]
cfg_aliases = "0.2"
Now you can use shorter cfg attributes:
// Before:
#[cfg(target_arch = "wasm32")]
// After:
#[cfg(wasm)]
// Before:
#[cfg(not(target_arch = "wasm32"))]
// After:
#[cfg(native)]
Automatic Event Handler Handling
The page! macro automatically handles event handlers for server-side rendering. You no longer need to write duplicate conditional blocks:
use reinhardt_pages::prelude::*;
// This works on both WASM and native targets!
// On WASM: Event handlers are bound to DOM events
// On native: Event handlers are automatically ignored
fn my_button(on_click: Signal<bool>) -> View {
page!(|| {
button {
@click: move |_| { on_click.set(true); },
"Click me"
}
})
}
Before (manual conditional compilation):
#[cfg(target_arch = "wasm32")]
{
page!(|| {
button {
@click: move |_| { on_click.set(true); },
"Click me"
}
})
}
#[cfg(not(target_arch = "wasm32"))]
{
let _ = on_click; // suppress warning
page!(|| {
button { "Click me" }
})
}
After (automatic handling):
// Just write once - the macro handles everything!
page!(|| {
button {
@click: move |_| { on_click.set(true); },
"Click me"
}
})
Reactive Conditional Rendering with watch
The watch { expr } syntax enables reactive re-rendering when Signal dependencies change. Unlike static if conditions that are evaluated only at render time, watch blocks automatically re-evaluate and update the DOM when their Signal dependencies change.
Why watch is Needed
When you extract Signal values before the page! macro, they become static:
// Problem: Static values don't update when Signal changes
let has_error = error.get().is_some(); // Static bool captured at render time
page!(|has_error: bool| {
if has_error { // This never re-evaluates!
div { "Error occurred" }
}
})(has_error)
The watch syntax solves this by creating a reactive context:
// Solution: Pass Signal directly and use watch
page!(|error: Signal<Option<String>>| {
watch {
if error.get().is_some() { // Re-evaluates when error changes!
div { { error.get().unwrap_or_default() } }
}
}
})(error.clone())
Signal-first Pattern
For reactive UIs, pass Signals directly to the page! macro instead of extracting values:
use reinhardt_pages::prelude::*;
fn error_display() -> View {
let (error, set_error) = use_state(None::<String>);
// Pass the Signal directly (not the extracted value)
let error_signal = error.clone();
page!(|error_signal: Signal<Option<String>>| {
watch {
if error_signal.get().is_some() {
div {
class: "alert-danger",
{ error_signal.get().unwrap_or_default() }
}
}
}
})(error_signal)
}
watch vs Static if
| Syntax | Use Case | Behavior |
|---|---|---|
if condition { ... } |
Static conditions, Copy types | Evaluated once at render time |
watch { if signal.get() { ... } } |
Signal-dependent conditions | Re-evaluates when Signal changes |
watch { match signal.get() { ... } } |
Multiple reactive branches | Re-evaluates when Signal changes |
Using watch with match
The watch block also supports match expressions:
page!(|state: Signal<AppState>| {
watch {
match state.get() {
AppState::Loading => div { "Loading..." },
AppState::Ready(data) => div { { data } },
AppState::Error(msg) => div { class: "error", { msg } },
}
}
})(state.clone())
Best Practices
- Pass Signals directly: Use
Signal<T>parameters instead of extracting values - Clone Signals:
Signal::clone()is cheap (Rc-based), so clone freely - Single expression:
watchblocks must contain exactly one expression - Avoid nesting: Don't nest
watchblocks (performance concern)
Architecture
This framework consists of several key modules:
reactive: Fine-grained reactivity system (Signal, Effect, Memo)dom: DOM abstraction layerbuilder: HTML element builder APIcomponent: Component system with IntoView traitform: Django Form integration (native only)csrf: CSRF protectionauth: Authentication integrationapi: API client with Django QuerySet-like interfaceserver_fn: Server Functions (RPC)ssr: Server-side renderinghydration: Client-side hydrationrouter: Client-side routing (reinhardt-urls compatible)platform: Platform abstraction typesprelude: Unified imports
Prelude Contents
The prelude includes:
Reactive System
Signal,Effect,Memo,Resource,ResourceState- Context:
Context,ContextGuard,create_context,get_context,provide_context,remove_context
Hooks
use_state,use_effect,use_memo,use_callback,use_contextuse_ref,use_reducer,use_transition,use_deferred_valueuse_id,use_layout_effect,use_effect_event,use_debug_valueuse_optimistic,use_action_state,use_shared_state,use_sync_external_store
Component System
Component,ElementView,IntoView,View,Props,ViewEventHandler
Events and Callbacks
Callback,IntoEventHandler,into_event_handlerEvent(platform-agnostic viaplatformmodule)
DOM
Document,Element,EventHandle,EventType,document
Routing
Link,Router,Route,RouterOutlet,PathPattern
API and Server Functions
ApiModel,ApiQuerySet,Filter,FilterOpServerFn,ServerFnError- See Server Function Macro Guide for detailed usage and migration information
Authentication and Security
AuthData,AuthError,AuthState,auth_stateCsrfManager,get_csrf_token
SSR and Hydration
HydrationContext,HydrationError,hydrateSsrOptions,SsrRenderer,SsrState
Forms (native only)
FormBinding,FormComponentWidget,FieldMetadata,FormMetadata
Macros
page!
WASM-specific
spawn_local(re-exported from wasm_bindgen_futures)create_resource,create_resource_with_deps
Example
use reinhardt_pages::prelude::*;
fn counter() -> View {
let (count, set_count) = use_state(|| 0);
page!(|| {
div {
p { format!("Count: {}", count.get()) }
button {
@click: move |_| set_count.update(|n| *n + 1),
"Increment"
}
}
})
}
Feature Flags
| Feature | Description |
|---|---|
msgpack |
MessagePack serialization support |
pages-full |
All features enabled (msgpack + web-sys-full) |
static |
Static file serving |
urls |
URL routing integration |
debug-hooks |
Debug hooks for development |
uuid |
UUID type support |
chrono |
Chrono date/time type support |
ast |
AST processing support |
web-sys-full |
All required web-sys features for WASM applications |
License
Licensed under the BSD 3-Clause License.
Dependencies
~41–87MB
~1.5M SLoC