leptos-rs/leptos · error

couldn't serialize island props

Error message

couldn't serialize island props

What it means

In the #[component] macro, when a component is an island that has props beyond those captured on the client (is_island_with_other_props), the macro generates code that serializes all props to JSON at runtime via serde_json::to_string(&props).expect("couldn't serialize island props"). This panics if any prop type is not JSON-serializable at runtime (e.g. contains non-serializable data like functions, channels, or unserializable custom types).

Source

Thrown at leptos_macro/src/component.rs:306

            #[cfg(not(feature = "tracing"))]
            {
                (quote!(), quote!(), quote!(), quote!())
            }
        };

        let component_id = name.to_string();
        let hydrate_fn_name = is_island.then(|| {
            use std::hash::{Hash, Hasher};

            let mut hasher = DefaultHasher::new();
            island.hash(&mut hasher);
            let caller = hasher.finish() as usize;
            Ident::new(&format!("{component_id}_{caller:?}"), name.span())
        });

        let island_serialize_props = if is_island_with_other_props {
            quote! {
                let _leptos_ser_props = ::leptos::serde_json::to_string(&props).expect("couldn't serialize island props");
            }
        } else {
            quote! {}
        };
        let island_serialized_props = if is_island_with_other_props {
            quote! {
                .with_props( _leptos_ser_props)
            }
        } else {
            quote! {}
        };

        let body_name = unmodified_fn_name_from_fn_name(&body_name);
        let body_expr = if is_island {
            quote! {
                ::leptos::reactive::owner::Owner::new().with(|| {
                    ::leptos::reactive::owner::Owner::with_hydration(move || {
                        ::leptos::tachys::reactive_graph::OwnedView::new({

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Make every island prop implement Serialize (and Deserialize for hydration); add #[derive(Serialize, Deserialize)] to custom types
  2. Remove non-serializable props (callbacks, Rc/NodeRef) from islands or pass only primitive/serializable data
  3. Avoid non-string map keys in props (use HashMap<String, _> not HashMap<MyEnum, _> unless keys serialize as strings)
  4. Handle serialization explicitly instead of relying on the macro's expect: serialize manually and pass as a String prop

Example fix

// before
#[component]
fn MyIsland(#[prop(into)] data: MyData) -> impl IntoView { ... } // MyData: no Serialize
// after
#[derive(Clone, serde::Serialize, serde::Deserialize)]
struct MyData { id: u32, name: String }
#[component]
fn MyIsland(data: MyData) -> impl IntoView { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate island props serializability in a test:
let _ = serde_json::to_string(&props)
    .unwrap_or_else(|e| panic!("island props not serializable: {e}"));

Type guard

fn is_island_serializable<T: serde::Serialize>(props: &T) -> bool {
    serde_json::to_string(props).is_ok()
}

Try / catch

// The expect panics; serialize yourself before the macro path and handle the Result:
match serde_json::to_string(&props) {
    Ok(json) => { /* pass json as a String prop */ }
    Err(e) => log::error!("island props serialization failed: {e}"),
}

Prevention

When it happens

Trigger: Declaring an #[component(island = true)] whose props include a type that fails Serialize at runtime — serialize returns Err (map keys not strings, unsupported types, poisoned serializer) — then hitting the generated expect during the island render path.

Common situations: Island props containing Rc/RefCell/callbacks or types with #[serde(skip)]-incompatible shapes; closures or generic types without Serialize bound; serde version mismatch producing non-string map keys.

Related errors


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