swc-project/swc · warning · Error
StrayEndTag
StrayEndTag
Error message
Stray end tag "{tag_name}" What it means
In the 'before head' insertion mode (crates/swc_html_parser/src/parser/mod.rs:1647), any end tag other than head/body/html/br is a parse error (StrayEndTag) and is ignored. The parser is past <html> but has not built <head> yet, so such closers match nothing.
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:1647
Token::StartTag { tag_name, .. } if tag_name == "head" => {
let element = self.insert_html_element(token_and_info)?;
self.head_element_pointer = Some(element);
self.insertion_mode = InsertionMode::InHead;
}
// An end tag whose tag name is one of: "head", "body", "html", "br"
//
// Act as described in the "anything else" entry below.
Token::EndTag { tag_name, .. }
if matches!(&**tag_name, "head" | "body" | "html" | "br") =>
{
anything_else(self, token_and_info)?;
}
// Any other end tag
//
// Parse error. Ignore the token.
Token::EndTag { tag_name, .. } => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::StrayEndTag(tag_name.clone()),
));
}
// Anything else
//
// Insert an HTML element for a "head" start tag token with no attributes.
//
// Set the head element pointer to the newly created head element.
//
// Switch the insertion mode to "in head".
//
// Reprocess the current token.
_ => {
anything_else(self, token_and_info)?;
}
}
}View on GitHub (pinned to 5176682b65)
Solutions
- Remove the stray end tag from the region between <html> and the first head/body content
- Let the parser auto-create head/body and place markup there
- Filter ErrorKind::StrayEndTag(_) if pre-head junk is tolerated
Example fix
<!-- before --> <html> </div> <p>x</p> </html> <!-- after --> <html> <p>x</p> </html>
Defensive patterns
Strategy: validation
Validate before calling
fn end_tag_before_head(html: &str) -> Option<usize> {
let lower = html.to_ascii_lowercase();
let html_end = lower.find("<html").map(|i| i + 5)?;
let head = lower.find("<head").unwrap_or(lower.len());
lower[html_end..head].find("</").map(|i| html_end + i)
} Try / catch
use swc_html_parser::error::ErrorKind;
let mut errors = Vec::new();
let doc = swc_html_parser::parse_file_as_document(&fm, config, &mut errors)?;
for err in &errors {
if let ErrorKind::StrayEndTag(tag) = err.kind() {
log::warn!("end tag </{}> before <head> ignored", tag);
}
} Prevention
- Keep the region between <html> and head content free of stray markup
- Generate document shells from one tested template
- Treat pre-head orphan closers as shell-template bugs
When it happens
Trigger: An end tag directly following the html start tag with no head started: `<html></div>`, `<html>\n</span>text`. The four exceptional names (head/body/html/br) take the 'anything else' path that implicitly creates <head>.
Common situations: Layout templates with stray closers left in the pre-head region, trimmed partials pasted after an explicit <html>, hand-written document shells.
Related errors
- Stray end tag "{tag_name}"
- End tag "{end_tag_name}" did not match the name of the curre
- End tag seen without seeing a doctype first, expected "<!DOC
- StrayDoctype
- SomethingSeenWhenSomethingOpen
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/5991141f739ebf25.
Report an issue: GitHub.