swc-project/swc · warning · swc_html_parser::error::Error
NonSpaceCharacterInTrailer
NonSpaceCharacterInTrailer
Error message
Non-space character in page trailer
What it means
A non-whitespace character token arrived in the 'after after body' insertion mode, i.e. visible text after </html>. This is the spec's 'non-space-character-in-trailer' parse error: the parser switches back to 'in body' and reprocesses, so the text is appended to body despite appearing after the document end. NonSpaceCharacterInTrailer records the misplaced character.
Source
Thrown at crates/swc_html_parser/src/parser/mod.rs:6591
Token::StartTag { tag_name, .. } if tag_name == "html" => {
self.process_token_using_rules(token_and_info, InsertionMode::InBody)?;
}
// An end-of-file token
//
// Stop parsing.
Token::Eof => {
self.stopped = true;
}
// Anything else
//
// Parse error. Switch the insertion mode to "in body" and reprocess the token.
_ => {
match token {
// Doctype handled above
// Comment handled above
// EOF handled above
Token::Character { .. } => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::NonSpaceCharacterInTrailer,
));
}
Token::StartTag { tag_name, .. } => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::StrayStartTag(tag_name.clone()),
));
}
Token::EndTag { tag_name, .. } => {
self.errors.push(Error::new(
token_and_info.span,
ErrorKind::StrayEndTag(tag_name.clone()),
));
}
_ => {
unreachable!();View on GitHub (pinned to 5176682b65)
Solutions
- Move the stray text before </body> in the source.
- Fix the builder/injector to insert content inside body rather than after </html>.
- If third-party and the recovered placement (text inside body) is fine, filter ErrorKind::NonSpaceCharacterInTrailer from the errors vec.
Example fix
<!-- before --> <html><body>main</body></html> appended text <!-- after --> <html><body>main appended text </body></html>
Defensive patterns
Strategy: validation
Validate before calling
// Non-whitespace after the last </html> is page-trailer content.
fn has_text_after(src: &str, close: &str) -> bool {
let lower = src.to_ascii_lowercase();
lower.rfind(close)
.map(|i| lower[i + close.len()..].chars().any(|c| !c.is_ascii_whitespace()))
.unwrap_or(false)
}
// has_text_after(&html, "</html>") Type guard
use swc_html_parser::error::{Error, ErrorKind};
fn is_non_space_in_trailer(err: &Error) -> bool {
matches!(err.kind(), ErrorKind::NonSpaceCharacterInTrailer)
} Try / catch
let mut errors = Vec::new();
let doc = parse_file_as_document(&fm, config, &mut errors)?;
if errors.iter().any(is_non_space_in_trailer) {
log::warn!("text after </html> was relocated into body");
} Prevention
- Files must end at </html> (plus trailing whitespace/newline).
- Fix append-to-file tooling that writes text after the document close.
- Add a CI assertion that the rendered page matches `(?s).*</html>\s*$`.
When it happens
Trigger: Text after the html close tag, e.g. `<html><body>x</body></html>trailing`. The 'anything else' arm of AfterAfterBody mode (crates/swc_html_parser/src/parser/mod.rs:6593) pushes the error, sets insertion_mode = InBody, and reprocesses the token.
Common situations: Tracking pixels and text nodes appended after </html> by append-to-file tooling; naive template concatenation; whitespace-trimming build steps that expose stray characters after the document close.
Related errors
- Non-space character after body
- Saw an end tag after "body" had been closed
- Non-space character in "frameset"
- Non-space character after "frameset"
- Non-space characters found without seeing a doctype first, e
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/79df58cd550d5d35.
Report an issue: GitHub.