mermaid-js/mermaid · error · Error

svg element not in render tree

Error message

svg element not in render tree

What it means

Thrown inside calculateTextDimensions when getBBox() on a freshly appended SVG text element returns width 0 AND height 0 — the SVG element is not in the render tree, so the browser cannot measure it. calculateTextDimensions appends an <svg> to document.body, draws text, measures, then removes it; if the document isn't a live rendering DOM (or the SVG never attaches), getBBox yields zeros.

Source

Thrown at packages/mermaid/src/utils.ts:728

    const g = body.append('svg');

    for (const fontFamily of fontFamilies) {
      let cHeight = 0;
      const dim = { width: 0, height: 0, lineHeight: 0 };
      for (const line of lines) {
        const textObj = getTextObj();
        textObj.text = line || ZERO_WIDTH_SPACE;
        // @ts-ignore TODO: Fix D3 types
        const textElem = drawSimpleText(g, textObj)
          // @ts-ignore TODO: Fix D3 types
          .style('font-size', _fontSizePx)
          .style('font-weight', fontWeight)
          .style('font-family', fontFamily);

        const bBox = (textElem._groups || textElem)[0][0].getBBox();
        if (bBox.width === 0 && bBox.height === 0) {
          throw new Error('svg element not in render tree');
        }
        dim.width = Math.round(Math.max(dim.width, bBox.width));
        cHeight = Math.round(bBox.height);
        dim.height += cHeight;
        dim.lineHeight = Math.round(Math.max(dim.lineHeight, cHeight));
      }
      dims.push(dim);
    }

    g.remove();

    const index =
      isNaN(dims[1].height) ||
      isNaN(dims[1].width) ||
      isNaN(dims[1].lineHeight) ||
      (dims[0].height > dims[1].height &&
        dims[0].width > dims[1].width &&
        dims[0].lineHeight > dims[1].lineHeight)

View on GitHub (pinned to d93e9c88c0)

Solutions

  1. Run rendering/measurement in a real browser or a DOM with layout (e.g. Playwright/Puppeteer with full Chromium), not plain jsdom.
  2. For SSR, precompute text dimensions client-side or use a browser-based render endpoint and ship the resulting SVG.
  3. If you must use jsdom, polyfill getBBox (e.g. via canvas measureText) or skip text-dimension caching.
  4. Ensure calculateTextDimensions is only called after document.body is available and the document is visible.

Example fix

// before — SSR with jsdom: getBBox returns 0x0
const dims = calculateTextDimensions(label, cfg); // throws
// after — render in a real browser (Playwright)
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setContent('<div id=d></div>');
const dims = await page.evaluate((l) => mermaid.calculateTextDimensions?.(l), label);
Defensive patterns

Strategy: try-catch

Validate before calling

function canMeasureText(): boolean {
  if (typeof document === 'undefined' || !document.body || !document.body.append) return false;
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  document.body.appendChild(svg);
  const t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  t.textContent = 'x'; svg.appendChild(t);
  const ok = t.getBBox().width > 0;
  document.body.removeChild(svg);
  return ok;
}
if (!canMeasureText()) throw new Error('current DOM cannot measure SVG text (use a real browser)');

Type guard

function isRenderableDom(d: Document): boolean { return typeof d.body?.append === 'function' && typeof (d.createElementNS('http://www.w3.org/2000/svg','svg') as any).getBBox === 'function'; }

Try / catch

try { return calculateTextDimensions(text, cfg); } catch (e) { if (/not in render tree/.test(String(e))) { return { width: text.length * 7, height: 14, lineHeight: 14 }; } throw e; }

Prevention

When it happens

Trigger: Running mermaid in jsdom/Node SSR where getBBox is unimplemented or returns zeros, in a detached document fragment, in a test environment without layout, or before the SVG is actually attached to document.body. The guard `if (!body.remove) return {0,0,0}` only catches missing remove(); it doesn't catch a present-but-not-laid-out DOM.

Common situations: Server-side rendering of mermaid diagrams (text dims need a real browser layout engine), jsdom-based unit tests, headless environments without a rendering pipeline, or calling text-measuring APIs before document.body exists. Real browsers don't hit this; non-rendering DOM hosts do.

Related errors


AI-assisted analysis of mermaid-js/mermaid@d93e9c88c0 (2026-08-12). Data as JSON: /api/errors/eb04e486f089a521. Report an issue: GitHub.