Skip to content

Mermaid download produces invalid SVG / fails for PNG when chart contains <br /> #516

@nekitk

Description

@nekitk

Bug Description

Downloading a Mermaid flowchart chart that contains <br/> in a label produces a broken file:

  • SVG opens with an XML parse error in the browser and is incomplete (stops rendering after br)
  • PNG is not downloaded without errors in console

In-page preview renders correctly, bug only affects the download.

Affected chart types:

  • flowchart
  • stateDiagram-v2

sequence and classDiagram are not affectd.

Steps to Reproduce

  1. Go to playground: https://streamdown.ai/playground
  2. Use this example:
    ```mermaid
    flowchart TD
      A["line 1<br/>line 2"] --> B["B"]
    ```
  3. Click download button
  4. Select "PNG"
  5. Select "SVG"
Image

Example for state diagram:

```mermaid
stateDiagram-v2                                                          
  A --> B: x<br/>y
```

Expected Behavior

  • Download PNG should work
  • Downloaded SVG should open without errors and be rendered in full

Actual Behavior

  • PNG - nothing happens
  • SVG - file is saved, but opens with error in browser, not fully rendered
Image

Code Sample

import { Streamdown } from "streamdown";
import { mermaid } from "@streamdown/mermaid";

const md = `
  \`\`\`mermaid                                                               
  flowchart TD                                           
      A["x<br/>y"]
  \`\`\`
`;

export default () => <Streamdown plugins={{ mermaid }}>{md}</Streamdown>;

Streamdown Version

2.5.0 (playground), 1.6.11 (first encountered in my project)

React Version

18.3.1 (my project)

Node.js Version

v24.6.0

Browser(s)

Chrome

Operating System

macOS

Additional Context

Core problem: mermaid.render() returns HTML, not XML. The problem with <br /> is that it is normalized to <br> (without closing slash) in browser. So then when it is saved to SVG as is, it comes as invalid XML.

Possible fixes:

    if (outputFormat === 'svg') {
      const svgXML = await page.$eval('svg', (svg) => {
        // SVG might have HTML <foreignObject> that are not valid XML
        // E.g. <br> must be replaced with <br/>
        // Luckily the DOM Web API has the XMLSerializer for this
        // eslint-disable-next-line no-undef
        const xmlSerializer = new XMLSerializer()
        return xmlSerializer.serializeToString(svg)
      })

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions