leptos-rs/leptos · error

you are using leptos_meta without a </head> tag

Error message

you are using leptos_meta without a </head> tag

What it means

During server-side rendering, leptos_meta rewrites the first HTML chunk to insert title/meta tags. It expects the string "</head>" to be present in that chunk and panics with this message if it cannot be found. The library throws it because the streaming-injection strategy requires a literal closing head tag to anchor insertion.

Source

Thrown at meta/src/lib.rs:251

            .map(|n| "<title>".len() + n.len() + "</title>".len())
            .unwrap_or(0);

        // collect all registered meta tags
        let meta_buf = self.elements.try_iter().collect::<String>();

        // get HTML strings for `<html>` and `<body>`
        let html_attrs = self.html.try_iter().collect::<String>();
        let body_attrs = self.body.try_iter().collect::<String>();

        let mut modified_chunk = if title_len == 0 && meta_buf.is_empty() {
            first_chunk
        } else {
            let mut buf = String::with_capacity(
                first_chunk.len() + title_len + meta_buf.len(),
            );
            let head_loc = first_chunk
                .find("</head>")
                .expect("you are using leptos_meta without a </head> tag");
            let marker_loc = first_chunk
                .find("<!--HEAD-->")
                .map(|pos| pos + "<!--HEAD-->".len())
                .unwrap_or_else(|| {
                    first_chunk.find("</head>").unwrap_or(head_loc)
                });
            let (before_marker, after_marker) =
                first_chunk.split_at_mut(marker_loc);
            buf.push_str(before_marker);
            buf.push_str(&meta_buf);
            if let Some(title) = title {
                buf.push_str("<title>");
                buf.push_str(&title);
                buf.push_str("</title>");
            }
            buf.push_str(after_marker);
            buf
        };

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Ensure the app template's first streamed chunk contains a literal "</head>" (lowercase, with the closing slash).
  2. Optionally include the "<!--HEAD-->" marker comment in <head> so leptos_meta can anchor injection there.
  3. Disable HTML minification/rewriting of the head in your SSR pipeline, or configure it to preserve "</head>" verbatim.
  4. Verify the head markup isn't split across stream chunks before leptos_meta processes it.

Example fix

<!-- before: transformed shell -->
<head><meta charset="utf-8"/>

<!-- after -->
<head><meta charset="utf-8"><!--HEAD--></head>
Defensive patterns

Strategy: validation

Validate before calling

if !first_chunk.contains("</head>") {
    return Err(anyhow!("SSR shell must contain a literal </head> for leptos_meta"));
}

Type guard

fn shell_has_head_close(html: &str) -> bool {
    html.contains("</head>")
}

Prevention

When it happens

Trigger: Calling from_app/inject_meta_context with an HTML template whose first chunk lacks the literal "</head>" — e.g. minified/transformed templates, templates using uppercase <HEAD>, or head content streamed in a later chunk.

Common situations: Custom React-like templates or framework wrappers that emit <head/> self-closed or omit the closing tag; build tools (minifiers, HTML rewriters) reformatting "</head>"; injecting leptos_meta into apps whose shell doesn't use a standard head.

Related errors


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