{"record":{"id":"eb04e486f089a521","repo":"mermaid-js/mermaid","slug":"svg-element-not-in-render-tree","errorCode":null,"errorMessage":"svg element not in render tree","messagePattern":"svg element not in render tree","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mermaid/src/utils.ts","lineNumber":728,"sourceCode":"\n    const g = body.append('svg');\n\n    for (const fontFamily of fontFamilies) {\n      let cHeight = 0;\n      const dim = { width: 0, height: 0, lineHeight: 0 };\n      for (const line of lines) {\n        const textObj = getTextObj();\n        textObj.text = line || ZERO_WIDTH_SPACE;\n        // @ts-ignore TODO: Fix D3 types\n        const textElem = drawSimpleText(g, textObj)\n          // @ts-ignore TODO: Fix D3 types\n          .style('font-size', _fontSizePx)\n          .style('font-weight', fontWeight)\n          .style('font-family', fontFamily);\n\n        const bBox = (textElem._groups || textElem)[0][0].getBBox();\n        if (bBox.width === 0 && bBox.height === 0) {\n          throw new Error('svg element not in render tree');\n        }\n        dim.width = Math.round(Math.max(dim.width, bBox.width));\n        cHeight = Math.round(bBox.height);\n        dim.height += cHeight;\n        dim.lineHeight = Math.round(Math.max(dim.lineHeight, cHeight));\n      }\n      dims.push(dim);\n    }\n\n    g.remove();\n\n    const index =\n      isNaN(dims[1].height) ||\n      isNaN(dims[1].width) ||\n      isNaN(dims[1].lineHeight) ||\n      (dims[0].height > dims[1].height &&\n        dims[0].width > dims[1].width &&\n        dims[0].lineHeight > dims[1].lineHeight)","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/mermaid-js/mermaid/blob/d93e9c88c01a599c062ee6a3f1462e3558ac6b90/packages/mermaid/src/utils.ts#L710-L746","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run rendering/measurement in a real browser or a DOM with layout (e.g. Playwright/Puppeteer with full Chromium), not plain jsdom.","For SSR, precompute text dimensions client-side or use a browser-based render endpoint and ship the resulting SVG.","If you must use jsdom, polyfill getBBox (e.g. via canvas measureText) or skip text-dimension caching.","Ensure calculateTextDimensions is only called after document.body is available and the document is visible."],"exampleFix":"// before — SSR with jsdom: getBBox returns 0x0\nconst dims = calculateTextDimensions(label, cfg); // throws\n// after — render in a real browser (Playwright)\nconst browser = await chromium.launch();\nconst page = await browser.newPage();\nawait page.setContent('<div id=d></div>');\nconst dims = await page.evaluate((l) => mermaid.calculateTextDimensions?.(l), label);","handlingStrategy":"try-catch","validationCode":"function canMeasureText(): boolean {\n  if (typeof document === 'undefined' || !document.body || !document.body.append) return false;\n  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n  document.body.appendChild(svg);\n  const t = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n  t.textContent = 'x'; svg.appendChild(t);\n  const ok = t.getBBox().width > 0;\n  document.body.removeChild(svg);\n  return ok;\n}\nif (!canMeasureText()) throw new Error('current DOM cannot measure SVG text (use a real browser)');","typeGuard":"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'; }","tryCatchPattern":"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; }","preventionTips":["Render in a real browser engine (Chromium via Playwright/Puppeteer), not jsdom, when text dims matter.","For SSR, render client-side or precompute dimensions.","Polyfill getBBox in jsdom if you must run there."],"tags":["utils","dom","ssr","jsdom","text-measurement"],"backgroundTag":null,"analyzedSha":"d93e9c88c01a599c062ee6a3f1462e3558ac6b90","analyzedAt":"2026-08-12T06:23:11.304Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}