leptos-rs/leptos · error
body to exist
Error message
body to exist
What it means
The <Portal/> component mounts its children into a mount target element; when none is given it falls back to document().body().expect("body to exist"). The panic means document.body was null when the Portal was created — the code is running in a DOM-less environment (SSR, or a script executed before <body> is parsed).
Source
Thrown at leptos/src/portal.rs:40
/// When using SVG this has to be set to `true`. Defaults to `false`.
#[prop(optional)]
is_svg: bool,
/// The children to teleport into the `mount` element
children: TypedChildrenFn<V>,
) -> impl IntoView
where
V: IntoView + 'static,
{
if cfg!(target_arch = "wasm32")
&& Owner::current_shared_context()
.map(|sc| sc.is_browser())
.unwrap_or(true)
{
use send_wrapper::SendWrapper;
use wasm_bindgen::JsCast;
let mount = mount.unwrap_or_else(|| {
document().body().expect("body to exist").unchecked_into()
});
let children = children.into_inner();
Effect::new(move |_| {
let container = if is_svg {
document()
.create_element_ns(Some("http://www.w3.org/2000/svg"), "g")
.expect("SVG element creation to work")
} else {
document()
.create_element("div")
.expect("HTML element creation to work")
};
let render_root = if use_shadow {
container
.attach_shadow(&web_sys::ShadowRootInit::new(
web_sys::ShadowRootMode::Open,View on GitHub (pinned to 32d20f6c9d)
Solutions
- Pass an explicit mount element to <Portal mount={...}/> so body is never needed.
- Only render <Portal/> on the client, e.g. inside a component gated on a mounted signal or hydration completion.
- Load your wasm script at the end of <body> or with defer so body exists.
- In tests, ensure the fixture creates and attaches document.body.
Example fix
// before
view! { <Portal><div/>"></Portal> } // SSR: body is None
// after
let mount = document().get_element_by_id("app");
view! { <Portal mount={mount}><div/>"></Portal> } // or gate on is_client
Defensive patterns
Strategy: validation
Validate before calling
if document().body().is_none() {
// skip Portal or wait for client mount
return;
}
Type guard
fn body_ready() -> bool { document().body().is_some() }
Prevention
- Always pass an explicit mount element to <Portal/>
- Load wasm scripts at end of <body> or with defer
- Gate Portal rendering behind a client-mounted flag for SSR apps
- In tests, create and attach document.body before rendering
When it happens
Trigger: Rendering <Portal/> without a mount prop during SSR; running client-side code from <head> before <body> exists; executing in a jsdom/test environment with no body attached.
Common situations: SSR hydration pass accidentally rendering Portal; scripts loaded with default (blocking) placement in <head>; wasm tests that never append a body to the document.
Related errors
- SVG element creation to work
- HTML element creation to work
- no shared context
- children should not be removed until we render here
- there to be a <body> element
AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01).
Data as JSON: /api/errors/2a10e0933852a39b.
Report an issue: GitHub.