yewstack/yew · error

failed to render application

Error message

failed to render application

What it means

ServerRenderer::render() runs the entire SSR pass on a spawned local task and receives the resulting String over a oneshot channel; this expect fires when rx.await returns Err, meaning the sender was dropped without sending. That happens only when the render task died - a panic inside component rendering, property creation, or a hook on the server side tore the task down before it could call tx.send(). The message is a symptom; the real error is the earlier panic in the server logs.

Source

Thrown at packages/yew/src/server_renderer.rs:262

            create_props,
            hydratable,
            rt,
        } = self;

        let (tx, rx) = futures::channel::oneshot::channel();
        let create_task = move || async move {
            let props = create_props();
            let s = LocalServerRenderer::<COMP>::with_props(props)
                .hydratable(hydratable)
                .render()
                .await;

            let _ = tx.send(s);
        };

        Self::spawn_rendering_task(rt, create_task);

        rx.await.expect("failed to render application")
    }

    /// Renders Yew Application to a String.
    pub async fn render_to_string(self, w: &mut String) {
        let mut s = self.render_stream();

        while let Some(m) = s.next().await {
            w.push_str(&m);
        }
    }

    #[inline]
    fn spawn_rendering_task<F, Fut>(rt: Option<Runtime>, create_task: F)
    where
        F: 'static + Send + FnOnce() -> Fut,
        Fut: Future<Output = ()> + 'static,
    {
        match rt {

View on GitHub (pinned to 0e4a05472f)

Solutions

  1. Read the server logs immediately above this message - the panic that killed the render task is the actual error
  2. Guard all browser access with if yew::is_browser() { ... } or #[cfg(target_arch = "wasm32")] blocks
  3. Unit-test create_props and component view code compiled for the server target
  4. If using with_runtime, keep the runtime alive until render() completes

Example fix

// before: panics on the server - there is no window during SSR
fn view(&self, _ctx: &Context<Self>) -> Html {
    let h = gloo::utils::window().inner_height();
    // ...
}

// after: branch on environment
fn view(&self, _ctx: &Context<Self>) -> Html {
    let h = if yew::is_browser() {
        Some(gloo::utils::window().inner_height())
    } else {
        None
    };
    // ...
}
Defensive patterns

Strategy: try-catch

Try / catch

// Turn the channel panic into a recoverable error and fall back to a CSR shell:
use futures::FutureExt;
use std::panic::AssertUnwindSafe;

let out = AssertUnwindSafe(renderer.render()).catch_unwind().await;
match out {
    Ok(html) => response.body(html),
    Err(_) => {
        tracing::error!("SSR task panicked; see panic log above");
        response.body(client_side_shell())
    }
}

Prevention

When it happens

Trigger: Any panic during server rendering: components calling browser-only APIs (gloo::utils::window()/document(), web_sys access) without guards; expects/unwraps inside create_props or view code; a custom runtime passed via with_runtime being dropped before the render completes.

Common situations: Porting a CSR app to SSR without is_browser()/cfg guards; unwrapping request data when building props; custom tokio runtimes shut down early; effects or hooks that assume a browser environment.

Related errors


AI-assisted analysis of yewstack/yew@0e4a05472f (2026-08-22). Data as JSON: /api/errors/0639e0544b35462d. Report an issue: GitHub.