leptos-rs/leptos · error
Expected a [number, string] tuple
Error message
Expected a [number, string] tuple
What it means
During client-side hydration, `serialized_errors()` reads the `__SERIALIZED_ERRORS` JS array injected by the server and expects every entry to be a `[number, string]`-shaped triple where index 2 is a JS string carrying the serialized error. If element 2 is not a string (e.g. `as_string()` returns None), the `.expect` panics in the WASM client, breaking hydration.
Source
Thrown at hydration_context/src/hydrate.rs:43
#[wasm_bindgen(thread_local)]
static __SERIALIZED_ERRORS: Array;
#[wasm_bindgen(thread_local)]
static __INCOMPLETE_CHUNKS: Array;
}
fn serialized_errors() -> Vec<(SerializedDataId, ErrorId, Error)> {
__SERIALIZED_ERRORS.with(|s| {
s.iter()
.flat_map(|value| {
value.dyn_ref::<Array>().map(|value| {
let error_boundary_id =
value.get(0).as_f64().unwrap() as usize;
let error_id = value.get(1).as_f64().unwrap() as usize;
let value = value
.get(2)
.as_string()
.expect("Expected a [number, string] tuple");
(
SerializedDataId(error_boundary_id),
ErrorId::from(error_id),
Error::from(SerializedError(value)),
)
})
})
.collect()
})
}
fn incomplete_chunks() -> Vec<SerializedDataId> {
__INCOMPLETE_CHUNKS.with(|i| {
i.iter()
.map(|value| {
let id = value.as_f64().unwrap() as usize;
SerializedDataId(id)
})View on GitHub (pinned to 32d20f6c9d)
Solutions
- Ensure server and hydration_context/throw_error crates are the same version (align leptos/leptos_config and hydration_context versions in Cargo.toml)
- Inspect the rendered HTML hydration markers and confirm each error entry is a [number, string] tuple; fix whatever produces object/array values at index 2
- Disable or configure any HTML-rewriting middleware/CDN minification that could alter the serialized error data
- If you construct test hydration data, serialize error messages with `.to_string()` so element 2 is always a JS string
Example fix
// before (custom error serialization on server) serde_json::to_value(my_error).unwrap() // object, not string // after my_error.to_string() // plain string, matches [number, string] tuple expectation
Defensive patterns
Strategy: type-guard
Validate before calling
// client-side, before trusting hydration data
let raw = js_sys::Reflect::get(&window(), &"__SERIALIZED_ERRORS".into()).ok();
let ok = raw.map(|v| {
js_sys::Array::from(&v).iter().all(|entry| {
let a = js_sys::Array::from(&entry);
a.length() >= 3 && a.get(2).is_string()
})
}).unwrap_or(false); Type guard
fn is_error_tuple(entry: &JsValue) -> bool {
entry
.dyn_ref::<js_sys::Array>()
.map(|a| a.length() >= 3 && a.get(2).is_string())
.unwrap_or(false)
} Try / catch
// wrap hydration bootstrap so a malformed payload degrades to client render
match std::panic::catch_unwind(|| hydrate()) {
Ok(_) => {},
Err(_) => mount_to_body(|view| { /* full client-side render */ }),
} Prevention
- Keep server and client hydration crates on identical versions
- Never hand-edit hydration markers in SSR output or snapshots
- Disable HTML rewriting/minification middleware that could corrupt hydration data
- Serialize error payloads with .to_string() so element 2 is always a JS string
When it happens
Trigger: Calling hydration entry points that collect serialized errors (e.g. Leptos hydration on the client) when the injected error payload has a non-string third element — typically a mismatch between server-side serialization code and client-side deserialization, hand-written or older hydration data, or corrupted/transformed hydration markers (HTML rewriters, CDNs, proxies mangling the payload).
Common situations: Version mismatch between server (leptos/Legerder serialization) and client hydration crates; custom middleware that rewrites hydration markers; an Error/CustomError serialized as a structured object instead of a string; manually edited SSR output in tests or static snapshots.
Related errors
- Reading from a LocalResource outside Suspense in `ssr` mode
- Unrecoverable hydration error. Please read the error message
- there to be a <html> element
- failed to read hash file
- Executor::spawn called, but no global 'spawn' function is co
AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01).
Data as JSON: /api/errors/7fd145b35873afad.
Report an issue: GitHub.