leptos-rs/leptos · critical
Unrecoverable hydration error. Please read the error message
Error message
Unrecoverable hydration error. Please read the error message directly above this for more details.
What it means
During client-side hydration, tachys attempts to cast an existing DOM node to the expected element type; if the node's type does not match what server rendering produced, hydration cannot continue safely. The framework logs detailed mismatch info via console.error/warn, then panics because proceeding would leave the app in an inconsistent state.
Source
Thrown at tachys/src/hydration.rs:184
{
let hydrating = CURRENTLY_HYDRATING
.take()
.map(|n| n.to_string())
.unwrap_or_else(|| "{unknown}".to_string());
web_sys::console::error_3(
&wasm_bindgen::JsValue::from_str(&format!(
"A hydration error occurred while trying to hydrate an \
element defined at {hydrating}.\n\nThe framework expected an \
HTML <{tag_name}> element, but found this instead: ",
)),
&node,
&wasm_bindgen::JsValue::from_str(
"\n\nThe hydration mismatch may have occurred slightly \
earlier, but this is the first time the framework found a \
node of an unexpected type.",
),
);
panic!(
"Unrecoverable hydration error. Please read the error message \
directly above this for more details."
);
}
}
pub(crate) fn failed_to_cast_marker_node(node: Node) -> Comment {
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
{
_ = node;
unreachable!();
}
#[cfg(any(debug_assertions, leptos_debuginfo))]
{
let hydrating = CURRENTLY_HYDRATING
.take()
.map(|n| n.to_string())
.unwrap_or_else(|| "{unknown}".to_string());View on GitHub (pinned to 32d20f6c9d)
Solutions
- Make the server and client render the identical view tree for the initial HTML (same branches, same ordering).
- Remove or fix conditional logic that diverges between SSR and hydration (e.g. cfg!, platform checks, time-based values).
- Check the console message printed directly above the panic to locate the actual mismatch point and align the element types.
- Ensure the ssr and hydrate feature sets match between the server and client builds.
Example fix
// before
#[cfg(feature = "ssr")]
view! { <div>{"server only"}</div> }
#[cfg(not(feature = "ssr"))]
view! { <span>{"client only"}</span> }
// after
view! { <div>{"shared"}</div> } Defensive patterns
Strategy: validation
Validate before calling
// Before hydrating, compare a checksum of server HTML structure vs client view
if !cfg!(feature = "ssr") || !cfg!(feature = "hydrate") { panic!("build must enable matching ssr/hydrate features"); } Type guard
fn is_element_of(node: &web_sys::Node, tag: &str) -> bool { node.dyn_ref::<web_sys::Element>().map(|e| e.tag_name().eq_ignore_ascii_case(tag)).unwrap_or(false) } Try / catch
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| hydrate_view())); // fall back to client render on hydration panic
Prevention
- Render identical view trees on server and client for the initial HTML.
- Keep ssr and hydrate feature flags in lockstep across builds.
- Avoid platform/time/locale-dependent values in the initial render.
- Never hand-edit or transform server-rendered HTML before hydration.
When it happens
Trigger: Calling hydrate (or inner_1 during traversal) where a DOM node fails the element type cast — e.g. server emitted <div> but client view expects <span>, or conditional/fragment structure shifted between server and client output.
Common situations: Server HTML generated by a different view tree than the client's first render (feature flags, platform-specific branches, nondeterministic data); missing or mismatched HydrationScript/keys; editing server-rendered HTML by hand.
Related errors
- Expected a [number, string] tuple
- Failed to find the route {path} requested by the user. This
- Static routes are not currently supported on WASM32 server t
- Reading from a LocalResource outside Suspense in `ssr` mode
- lazy routes should not be used with hydrate_body(); use hydr
AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01).
Data as JSON: /api/errors/3f0179d8190f8e95.
Report an issue: GitHub.