swc-project/swc · warning · Error
Saw an end tag after "body" had been closed
Error message
Saw an end tag after "body" had been closed
What it means
An end tag other than </html> appeared in the 'after body' insertion mode. This is the spec's parse error reported as ErrorKind::EndTagAfterBody; the parser switches to 'in body' and reprocesses the token, so normal end-tag handling applies afterwards.
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:6271
_ => {
match token {
// Doctype handled above
// Comment handled above
// EOF handled above
Token::Character { .. } => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::NonSpaceCharacterAfterBody,
));
}
Token::StartTag { tag_name, .. } => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::StrayStartTag(tag_name.clone()),
));
}
Token::EndTag { .. } => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::EndTagAfterBody,
));
}
_ => {
unreachable!();
}
}
self.insertion_mode = InsertionMode::InBody;
self.process_token(token_and_info, None)?;
}
}
}
// The "in frameset" insertion mode
InsertionMode::InFrameset => {
// When the user agent is to apply the rules for the "in frameset" insertion
// mode, the user agent must handle the token as follows:View on GitHub (pinned to 5176682b65)
Solutions
- Remove or relocate the stray closing tag so every end tag sits inside body.
- Re-balance the markup: ensure each open tag inside <body> is closed before </body>.
- Add an HTML validation step (validator.nu or html5ever-based linter) to CI for template sources.
- If tolerated, filter ErrorKind::EndTagAfterBody from the errors vec; the parser recovers.
Example fix
<!-- before --> <body><div><p>x</div></p></body></div></html> <!-- after --> <body><div><p>x</p></div></body></html>
Defensive patterns
Strategy: validation
Validate before calling
// Any non-html end tag after the last </body> is a stray close.
fn has_end_tag_after_body(src: &str) -> bool {
let lower = src.to_ascii_lowercase();
match lower.rfind("</body>") {
None => false,
Some(i) => lower[i..].matches("</").count() > 1, // </body> itself + more
}
} Type guard
use swc_html_parser::error::{Error, ErrorKind};
fn is_end_tag_after_body(err: &Error) -> bool {
matches!(err.kind(), ErrorKind::EndTagAfterBody)
} Try / catch
let mut errors = Vec::new();
let doc = parse_file_as_document(&fm, config, &mut errors)?;
if errors.iter().any(is_end_tag_after_body) {
log::warn!("end tag seen after body close; reprocessed in body");
} Prevention
- Close every element opened inside body before </body>.
- Validate template balance with an HTML linter in CI.
- Watch for copy-pasted fragments that carry their own closing tags.
When it happens
Trigger: A stray closing tag after </body>, e.g. `</body></div>` or `</body></p>`. The 'anything else' arm of AfterBody mode matches Token::EndTag and pushes EndTagAfterBody.
Common situations: Templates whose macro/layout closes a wrapper element outside the body region; unbalanced markup where an outer container was closed early; copy-paste of page fragments that carry their own closing tags.
Related errors
- Non-space character after body
- NonSpaceCharacterInTrailer
- End tag "{end_tag_name}" did not match the name of the curre
- Stray end tag "{tag_name}"
- End tag seen without seeing a doctype first, expected "<!DOC
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/18464f52cf59d528.
Report an issue: GitHub.