Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
halyard
halyard is a full-stack Rust web framework: server-side rendering, hydration and
fine-grained reactivity, with an axum integration. It is its own project. It began on
2026-09-22 as a fork of Leptos 0.8.20 (MIT, © 2022
Greg Johnston — see LICENSE and NOTICE) and no longer tracks
it. Every crate is renamed (leptos → halyard, leptos_router → halyard_router,
tachys → halyard_tachys, …; full map below) and published on crates.io under those
names (halyard = "0.1"); its build tool is
cargo-halyard.
Why it began as a fork
We hit six defects in production (reproduced in Chromium and WebKit) that are better fixed at the source than worked around in every application:
- WASM file name baked in at compile time. Upstream decided between
<name>.wasmand<name>_bg.wasmwithoption_env!("LEPTOS_OUTPUT_NAME"), so a server built by plaincargo buildrequested a file the build tool never wrote (404, hydration never ran). halyard resolves the name at runtime fromHalyardOptions(newwasm_file_nameoption, defaultoutput_name); nooption_env!/env!influences rendered output any more (leptos/src/hydration/mod.rs,leptos_config). --cfg erase_componentsmismatch. Debug builds run through the build tool are compiled with--cfg erase_components, which changes the hydration marker comments the server emits; a server built without it made the client panic with "expected a marker node". halyard records the server's mode in a<meta name="halyard-render-mode">tag and the client checks it before hydrating: on a mismatch it logs one clear error naming both modes and how to fix it, then skips hydration — no panic (leptos/src/hydration/mod.rs,leptos/src/mount.rs,leptos/tests/render_mode.rs).- Hydration mismatches were panics with useless context. halyard logs the view's
source location (in debug /
--cfg halyard_debuginfobuilds), what was expected, what was found (tag/text snippet) and the DOM path, then abandons hydration cleanly and renders the app on the client instead. The old behaviour is behind thepanic-on-hydration-mismatchfeature (tachys/src/hydration.rs). - Bootstrap script had no rejection handler (WebKit: "Unhandled Promise Rejection:
TypeError: Load failed" when navigating away mid-load). The inline script now ends in a
.catchthat logs one conciseconsole.warn. - WebKit downloaded the WASM twice because
<link rel="preload" as="fetch">is not matched against wasm-bindgen'sfetch(). halyard drops the preload and instead starts thefetch()itself, immediately, from a classic inline script, handing the pendingResponsetoinit— exactly one request in every browser, started as early as the preload was. - Two unmaintained proc-macro helpers.
pasteis replaced bypastey;proc-macro-error2is replaced byhalyard_macro_diagnostics(a few dozen lines onsyn::Error::to_compile_error). Becauserstmlpulledproc-macro-error2in throughsyn_derive, both are vendored underthird_party/with that dependency removed. Neither crate is in the lockfile.
Crate map
| upstream (dir kept) | halyard |
|---|---|
leptos (leptos/) |
halyard |
leptos_macro |
halyard_macro |
leptos_router (router/) |
halyard_router |
leptos_router_macro (router_macro/) |
halyard_router_macro |
leptos_meta (meta/) |
halyard_meta |
leptos_axum (integrations/axum/) |
halyard_axum |
leptos_actix (integrations/actix/) |
halyard_actix |
leptos_integration_utils (integrations/utils/) |
halyard_integration_utils |
leptos_server, leptos_config, leptos_dom, leptos_hot_reload |
halyard_server, halyard_config, halyard_dom, halyard_hot_reload |
tachys |
halyard_tachys |
reactive_graph, reactive_stores, reactive_stores_macro |
halyard_reactive_graph, halyard_reactive_stores, halyard_reactive_stores_macro |
hydration_context |
halyard_hydration_context |
server_fn, server_fn_macro, server_fn_macro_default |
halyard_server_fn, halyard_server_fn_macro, halyard_server_fn_macro_default |
any_spawner, either_of, next_tuple, or_poisoned, const_str_slice_concat |
halyard_any_spawner, halyard_either_of, halyard_next_tuple, halyard_or_poisoned, halyard_const_str_slice_concat |
oco_ref (oco/) |
halyard_oco |
throw_error (any_error/) |
halyard_throw_error |
| — (new) | halyard_macro_diagnostics |
rstml 0.12.1, syn_derive 0.2.0 (vendored, third_party/) |
halyard_rstml, halyard_syn_derive |
Inside halyard the re-export names are unchanged: halyard::tachys, halyard::server_fn,
halyard::reactive, halyard::prelude::*, and the view!, #[component], #[server]
macros keep their names. Types named Leptos* are now Halyard* (HalyardOptions,
HalyardRoutes, …).
Configuration
Every runtime setting is read from HALYARD_<NAME> with the legacy LEPTOS_<NAME> as a
fallback (HALYARD_OUTPUT_NAME / LEPTOS_OUTPUT_NAME, ..._SITE_ROOT, ..._SITE_PKG_DIR,
..._SITE_ADDR, ..._RELOAD_PORT, ..._ENV, ..._HASH_FILES, ..._HASH_FILE_NAME,
..._WATCH, and the new ..._WASM_FILE_NAME), so existing cargo-leptos configurations and
deployments keep working. Likewise get_configuration(Some("Cargo.toml")) reads
[package.metadata.halyard] and falls back to [package.metadata.leptos].
Checks
What CI runs (.github/workflows/ci.yml), each crate tested on its own (see the known
issue below):
RUSTFLAGS="--cfg erase_components"
Before a release, cargo package --workspace packages and verifies every crate against
crates.io the way cargo publish will. A dev-dependency on a crate that is published
later must be path-only (no version), or the publish of the earlier crate fails.
examples/ssr_modes_axum is kept as an SSR + hydration smoke test for the sibling build
tool (cargo-halyard).
The Leptos book (https://book.leptos.dev/) still describes most of the API, apart from
the crate names and the changes listed above. See ARCHITECTURE.md
for how the crates fit together.
Project policy
halyard is our framework, taken wherever the applications built on it need; it does not follow Leptos.
- Refactor freely. Rename, restructure, delete and redesign. Nothing is kept mergeable with Leptos, and nothing is merged or ported from it on a schedule.
- Improve as we go. When an application needs something (a clearer error, a safer default, a missing hook), change halyard rather than working around it.
- No panics, ever. A panic in the browser kills the application (release wasm is
built with
panic = "abort"); on the server it fails a request. Library code returns typed errors (Result,thiserrorenums) or recovers visibly (log, fall back to client rendering, render nothing), and uses the strongest types that are practical so the impossible states cannot be written. The inherited code does not meet this yet:scripts/panic-ratchet.shcounts everyunwrap,expect,panic!,unreachable!,todo!, unchecked index and unchecked arithmetic per crate, CI fails if a count rises, and each crate's lints go todenyonce its count reaches zero. The hydration and server request paths go first. - Never write to Leptos. The old
upstreamremote is push-disabled; we do not open issues or pull requests there. - Attribution stays.
LICENSE(MIT, © 2022 Greg Johnston) andNOTICEtravel with every copy.
Known issues (fork backlog)
cargo test --workspacefails 8 targets because Cargo unifies thesandboxed-arenasfeature (enabled by the axum integration's tests) into crates whose tests assume it is off. Every crate passes when tested on its own (cargo test -p <crate>), which is how CI runs them. Inherited from upstream; to be fixed by making those tests feature-aware.