日本語

A web framework on axum

The page, its behavior, and the server, all in Rust

cargo build builds it, with no Node and no wasm, and a mismatch between the page and the server is a compile error.

No browser build

cargo build builds it, with no Node and no wasm. The crate embeds a prebuilt runtime of about 14 KB gzipped.

Typed from page to server

Signal names, expressions, actions, and links come from Rust types. A link whose page has no handler, or whose path parameters or query do not fit the page, is a compile error, as is any other mismatch between the page and the server.

Optimistic updates with no extra code

A statement changes the page in the browser, and an action call sent with it reaches the server after. Changing the page before the server answers takes no extra code.

Styles, components, and translations, too

hypersig is the framework. Three crates in the same repository add typed styles, a set of accessible components, and typed translations, each with docs of its own.

Write the page in Rust

The signal struct holds the page's state. Counter::count() is a typed reference to its field, and count.set(count + 1) is a statement the browser runs on a click without asking the server. The server renders the page with the count already filled in, so the page shows before the runtime loads.

Result

counter.rs
use hypersig::html::{div, span};
use hypersig::style::Style;
use hypersig::style::pattern::hstack;
use hypersig::style::value::{FontWeight, TextAlign, rem};
use hypersig::{Page, Signals};
use hypersig_ui::{ButtonSize, ButtonVariant, button};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
pub struct Counter {
    count: i32,
}

pub fn page() -> Page<Counter> {
    let count = Counter::count();
    Page::new(
        "Counter",
        Counter { count: 0 },
    )
    .child(
        div()
            .css(hstack(rem(1)))
            .child(
                button(ButtonVariant::Outline, ButtonSize::Default)
                    .on_click(count.set(count - 1))
                    .text("-1"),
            )
            .child(
                span()
                    .css(
                        Style::new()
                            .min_width(rem(3))
                            .text_align(TextAlign::Center)
                            .font_size(rem(1.5))
                            .font_weight(FontWeight::Number(600.0)),
                    )
                    .bind_text(count.to_text()),
            )
            .child(
                button(ButtonVariant::Default, ButtonSize::Default)
                    .on_click(count.set(count + 1))
                    .text("+1"),
            ),
    )
}

How it compares

Topcoat and Datastar come closest to hypersig. Both keep signals in the browser and update the page from the server with no browser build, and Topcoat writes the browser's expressions in Rust too.

Topcoat and Datastarhypersig
Signal names and URLs (Datastar)Strings in HTML attributes. A typo shows up when the browser runs itWritten from Rust values. A typo stops cargo build
Link parameters (Topcoat)href! checks only the handler. A missing or extra path parameter panics when the page renders, and the request answers 500Route takes its parameters as a typed value. A wrong one is a compile error
Adding to a list (Topcoat)Expressions cannot add an item, so the list changes once the server answers and renders it againThe list is a signal, so the browser shows the change before the server answers
hypersigTopcoatReactNext.jsTanStack StartNuxtDatastar
Page written inRustRustTypeScriptTypeScriptTypeScriptTypeScript and Vue templatesHTML attributes
Server written inRustRustAnyTypeScriptTypeScriptTypeScriptAny
No browser buildYesYesNo (Vite)No (Node bundler)No (Vite)No (Vite)Yes
Request types checked at compile timeYesYesNoYesYesNoNo
Links checked against routes at compile timeYesHandler only (parameters at run time)No (no router)Opt-in (typedRoutes)YesExperimental (typedPages)No
State kept in the browserYesYesYesYesYesYesYes

Benchmarks

A 2000-item TodoMVC that stores every add, toggle, and delete on the server. Medians of 3 runs in Chrome 155 on an Apple M4 Max, with the CPU slowed 4x and 50 ms added to every request. The apps and the harness are in bench/compare.

hypersigTopcoatReactNext.jsTanStack StartNuxtDatastar
JS (gzip)19.2 KB14.8 KB67.1 KB133.3 KB119.5 KB59.2 KB13.1 KB
First Contentful Paint186 ms165 ms196 ms198 ms193 ms177 ms182 ms
Total Blocking Time0 ms313 ms5 ms10 ms0 ms29 ms16 ms
Interaction to Next Paint24 ms24 ms24 ms24 ms24 ms16 ms16 ms

The same page styled with hypersig::style, Tailwind CSS, Panda CSS, StyleX, and vanilla-extract is measured on the Styling page.