Skip to content

theme-mermaid: some diagrams render as an empty SVG after a page refresh when a page has many diagrams (parallel initialize + render) #12757

Open

Description

@kdelinx

Summary

One docs page with 12 Mermaid diagrams (9 flowcharts with the ELK layout, 3 sequence diagrams). After a hard refresh only 6 or 7 of the 12 are drawn, a different subset each time; the rest stay empty with no error. Rendering the same 12 sources directly with mermaid.render() always works, and running the theme's initialize + render calls through a single queue makes all 12 render reliably.

Description

On a page with many Mermaid diagrams (12 here, 9 flowcharts using the ELK layout and 3 sequence diagrams), a hard refresh leaves a random subset of the diagrams empty. There is no error in the console and no error boundary: the container <div class="docusaurus-mermaid-container"> is rendered, but its innerHTML is an empty string, so renderResult.svg was "".

What I observed on the production build (docusaurus build + docusaurus serve), reloading the same page repeatedly:

  • Sometimes 6 of 12 diagrams were rendered, sometimes 7 of 12, with different ones missing each time.
  • Waiting longer never fixed it: the empty ones stayed empty.
  • Rendering the same 12 sources directly with mermaid.render() from the same Mermaid version, in parallel, in the browser console, always worked. So the diagram sources are fine.

Suspected cause

renderMermaid in packages/docusaurus-theme-mermaid/src/client/index.ts calls the global mermaid.initialize(config) and then mermaid.render(id, text), once per diagram, from a useEffect, and all diagrams on the page start at the same time. Mermaid is a mutable singleton, and initialize() replaces its site config. config comes from useMermaidConfig(), which depends on colorMode; if the color mode changes after hydration, the config is recreated, every effect runs again, and initialize() is called again while renders from the previous round may still be in flight. With a heavier async layout like ELK a render takes long enough for this to overlap. The renders that were in flight resolve with an empty SVG.

I have not bisected this inside Mermaid itself, and I did not confirm that a color mode change is what triggers it in my setup. "Re-initialize during an in-flight render" is my explanation, supported by the fact that serializing the calls fixes it (see below), not something I proved from Mermaid's source.

Reproduction

I do not have a minimal standalone repro yet. The setup where it happens:

  1. Docusaurus 3.10.2 with markdown.mermaid: true and themes: ["@docusaurus/theme-mermaid"].
  2. @mermaid-js/layout-elk installed (so ELK is registered automatically), Mermaid 12.0.0.
  3. One doc page with about 12 mermaid code blocks. Nine are flowchart with
    ---
    config:
      layout: elk
    ---
    flowchart LR
    
    and a handful of subgraphs and 5 to 25 edges; three are sequenceDiagram.
  4. docusaurus build && docusaurus serve, open the page, hard refresh a few times, and count .docusaurus-mermaid-container elements with a non-empty <svg>.

I can turn this into a minimal repo if it helps.

Workaround

Swizzling Mermaid and running every initialize + render pair through one promise chain makes it reliable (12/12 on repeated refreshes, with ELK):

let renderQueue = Promise.resolve();

function enqueueRender({id, text, config}) {
  const run = async () => {
    const mermaid = await loadMermaid();
    mermaid.initialize(config);
    try {
      return await mermaid.render(id, text);
    } catch (error) {
      document.querySelector(`#d${id}`)?.remove();
      throw error;
    }
  };
  const result = renderQueue.then(run, run);
  renderQueue = result.catch(() => {});
  return result;
}

The hook uses this instead of calling renderMermaid directly, and ignores a result if its effect was cleaned up (cancelled flag).

Expected behavior

Every diagram on the page renders, whatever the number of diagrams and layout engine, and whether or not the color mode changes after hydration.

Possible fix

Serialize initialize + render in theme-mermaid as above (module-level queue), or at least make the pair atomic. I am happy to send a PR if you think this is the right direction; the cost is that diagrams render one after another instead of all at once.

Environment

  • Docusaurus: 3.10.2 (@docusaurus/core, @docusaurus/theme-mermaid)
  • Mermaid: 12.0.0, @mermaid-js/layout-elk: 1.0.0
  • Node: 22.19.0
  • Browser: Chrome, macOS
  • Production build served with docusaurus serve

Related, but not the same

Activity

  1. kdelinx commented on Oct 25, 2026

    @kdelinx
    Author

    looks like good, could be merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions