emberjs/ember.js · error

Invalid end tag: closing tag must not be self-closing

Error message

Invalid end tag: closing tag must not be self-closing

What it means

Self-closing syntax (/>) is only valid on start (opening) tags. When markTagAsSelfClosing runs and the current tag is an EndTag — e.g. </div/> — the tokenizer cannot mark a closing tag as self-closing, so it throws a syntax error spanning the tag.

Source

Thrown at packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts:199

      element.closeTag = null;
    } else if (element.selfClosing) {
      assert(element.closeTag === null, 'element.closeTag unexpectedly present');
    } else {
      element.closeTag = closeTagStart.until(this.offset());
    }

    element.loc = element.loc.withEnd(this.offset());

    appendChild(parent, b.element(element));
  }

  markTagAsSelfClosing(): void {
    let tag = this.currentTag;

    if (tag.type === 'StartTag') {
      tag.selfClosing = true;
    } else {
      throw generateSyntaxError(
        `Invalid end tag: closing tag must not be self-closing`,
        this.source.spanFor({ start: tag.start.toJSON(), end: this.offset().toJSON() })
      );
    }
  }

  // Tags - name

  appendToTagName(char: string): void {
    let tag = this.currentTag;
    tag.name += char;

    if (tag.type === 'StartTag') {
      let offset = this.offset();

      if (tag.nameStart === null) {
        assert(tag.nameEnd === null, 'nameStart and nameEnd must both be null');

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Remove the trailing slash from the closing tag: </div/> → </div>.
  2. If the element was meant to be self-closing, put the slash on the start tag instead and delete the separate end tag: <img src="x.png" /> with no </img>.
  3. Re-check the surrounding lines for duplicated '/' characters left over from an edit.

Example fix

// before
<div class="box">
  content
</div/>

// after
<div class="box">
  content
</div>
Defensive patterns

Strategy: validation

Validate before calling

// reject self-closing end tags before compiling
function assertNoSelfClosingEndTags(template) {
  const bad = template.match(/<\s*\/\s*[A-Za-z][^>]*\/\s*>/g);
  if (bad) throw new Error(`Closing tag must not be self-closing: ${bad.join(', ')}`);
}
assertNoSelfClosingEndTags(template);

Prevention

When it happens

Trigger: Writing a closing tag with a trailing slash: </div/>, </ul />, </:slot/>. The parser invokes markTagAsSelfClosing when it encounters '/' inside the tag; since tag.type is 'EndTag' the else branch throws.

Common situations: Typos where the self-closing slash meant for a start tag (<br/>) drifted onto the end tag; hand-editing templates and leaving </div/>; codemods or string concatenation producing malformed close tags.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/2ade3f89b880a354. Report an issue: GitHub.