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

  1. Make the server and client render the identical view tree for the initial HTML (same branches, same ordering).
  2. Remove or fix conditional logic that diverges between SSR and hydration (e.g. cfg!, platform checks, time-based values).
  3. Check the console message printed directly above the panic to locate the actual mismatch point and align the element types.
  4. 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

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


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/3f0179d8190f8e95. Report an issue: GitHub.