leptos-rs/leptos · error

You cannot use Suspend on an attribute outside Suspense

Error message

You cannot use Suspend on an attribute outside Suspense

What it means

An async (Suspend-wrapped) class attribute cannot produce its value synchronously. During server HTML string generation (to_html), the framework calls now_or_never(); if the future is not ready, rendering is outside a Suspense context that would otherwise await it, so it panics.

Source

Thrown at tachys/src/reactive_graph/class.rs:985

impl<Fut> IntoClass for Suspend<Fut>
where
    Fut: Clone + Future + Send + 'static,
    Fut::Output: IntoClass,
{
    type AsyncOutput = Fut::Output;
    type State = Rc<RefCell<Option<<Fut::Output as IntoClass>::State>>>;
    type Cloneable = Self;
    type CloneableOwned = Self;

    fn html_len(&self) -> usize {
        0
    }

    fn to_html(self, style: &mut String) {
        if let Some(inner) = self.inner.now_or_never() {
            inner.to_html(style);
        } else {
            panic!("You cannot use Suspend on an attribute outside Suspense");
        }
    }

    fn hydrate<const FROM_SERVER: bool>(
        self,
        el: &crate::renderer::types::Element,
    ) -> Self::State {
        let el = el.to_owned();
        let state = Rc::new(RefCell::new(None));
        reactive_graph::spawn_local_scoped({
            let state = Rc::clone(&state);
            async move {
                *state.borrow_mut() =
                    Some(self.inner.await.hydrate::<FROM_SERVER>(&el));
                self.subscriber.forward();
            }
        });
        state

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Wrap the containing view in <Suspense> so the async attribute can be awaited during SSR.
  2. Make the class value synchronous for the initial render (resolve the data before rendering, or use a sync signal).
  3. If async data is required, move the async read inside a Suspense boundary or use await Suspending() in the component body (leptos).

Example fix

// before
view! { <div class=Suspend(async move { load_class().await }) /> }
// after
view! {
  <Suspense fallback=|| ()>
    <div class=Suspend(async move { load_class().await }) />
  </Suspense>
}
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_suspense_context(rendering_to_html: bool, inside_suspense: bool) { if rendering_to_html && !inside_suspense { panic!("async class attribute requires a Suspense boundary"); } }

Type guard

fn class_is_sync(c: &impl Fn() -> String) {} // prefer sync closures/signals over Suspend for attributes

Try / catch

std::panic::catch_unwind(|| view_to_html(&view)).map_err(|_| /* rebuild view inside Suspense */)

Prevention

When it happens

Trigger: Using Suspend (async-derived class value) as a class attribute while rendering to HTML (SSR/dry-run) without the attribute being inside a <Suspense> component.

Common situations: Making a class reactive/async in a component rendered during SSR but forgetting to wrap the view in Suspense; refactoring an attribute to an async source without updating the rendering context.

Related errors


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