DEV Community

xiaoxu
xiaoxu

Posted on

Why I Render Mermaid Before Uploading Markdown Images

Why I Render Mermaid Before Uploading Markdown Images

Why this matters

Diagrams are easy to treat as decoration until an automated publisher has to move an article between three different representations: authoring Markdown, publishable local assets, and delivery URLs.

In this project, Mermaid is intentionally part of the authoring source. The publishing pipeline, however, only processes Markdown image nodes. That creates a boundary worth making explicit: a Mermaid code fence must become a local image before image optimization and upload begin.

The focused question is: how can a Markdown publisher make diagram rendering reproducible without mixing generated URLs back into the authoring source?

What I built or tested

The repository uses three artifacts for one article:

Artifact Purpose Safe rerun input?
source.md Human-authored Markdown with Mermaid blocks Yes, for rendering
index.md Rendered Markdown with local PNG references Yes, for publishing
published.md Archival Markdown with delivery URLs No

The renderer scans fenced mermaid blocks, assigns each one a deterministic filename from its order and content hash, writes its source beneath the article's image directory, and asks Mermaid CLI for a transparent PNG. It checks that the PNG exists, then replaces the fence with a relative Markdown image link in index.md.

That sequence leaves the authoring document readable while handing the publisher exactly the input it understands.

Setup

The example uses a Node.js TypeScript publisher with Mermaid CLI available locally. An article needs ordinary publisher-compatible front matter and one or more Mermaid fences. In normal authoring, the fence begins with the literal mermaid ` marker and ends with `; the pipeline diagram below is one such fence.

The render command writes a different file rather than editing the source in place:

npm run article:render -- articles/my-article/source.md \
  --output-markdown articles/my-article/index.md
Enter fullscreen mode Exit fullscreen mode

That output file is the input for asset preparation or publication.

Step-by-step walkthrough

1. Keep the diagram as source until rendering

The renderer finds fenced Mermaid blocks and computes a short hash from normalized diagram text. The output name also includes the diagram's position, so two distinct blocks do not collide merely because they have similar labels.

The original source stays in source.md. The generated Mermaid text is kept privately beside the generated images, which is useful when a diagram needs to be regenerated with the same toolchain.

2. Replace the fence with a local, relative asset

After Mermaid CLI creates the PNG, the renderer verifies that the expected file exists. Only then does it write a Markdown image link such as ./images/diagram-01-<hash>.png into index.md.

Mermaid diagram 1

The important contract is the arrow from rendered Markdown to image preparation. The uploader never needs to parse Mermaid syntax or know how a diagram was created; it receives an ordinary local image reference.

3. Prepare images only after the boundary is crossed

The publisher parses Markdown, visits image nodes, and rejects paths that are absolute, external, data URLs, query-bearing, or outside the article directory. For a permitted image it optimizes the file and caches the upload by provider and object key.

It also deduplicates repeated references to the same source image during one publish operation. This makes index.md a small, deterministic contract: all publishable visual inputs are local files below the article directory.

4. Publish the rendered input, archive the URL form

During a dry run, the publisher uses preview storage and in-memory publication state. A real run uses the configured storage provider and persists upload records. In both cases, the image replacement happens before the platform publish input is constructed.

The final Markdown with delivery URLs is useful for archival and inspection, but it is not the rerun input. Re-rendering starts from source.md; re-publishing starts from the local-asset index.md.

What went wrong

The tempting shortcut is to send source.md directly to image preparation. It looks harmless because Mermaid is valid Markdown code fencing.

But a fenced diagram is not a Markdown image node. The uploader walks image nodes only, so it has no local file to optimize or upload. The platform would receive source text instead of a managed diagram asset. Conversely, putting external delivery URLs back in the authoring source would make later renders and local verification depend on an already-published artifact.

Rendering is also a real dependency boundary. Mermaid CLI needs a working browser runtime. This implementation treats a missing expected PNG as an error rather than emitting index.md with a broken link.

Fix or mitigation

Use a two-phase handoff:

  1. Author Mermaid in source.md.
  2. Render it into index.md and inspect the local PNG.
  3. Run a publisher dry run against the rendered file.
  4. Prepare assets and publish only from index.md.
  5. Retain published.md as a record of the externally addressable result.

This separation also gives an AI coding agent a safe review boundary. It can draft or revise the Mermaid source, while the renderer and publisher enforce the deterministic parts of the transformation.

Trade-offs

This design creates more files and requires Mermaid CLI in every environment that renders an article. Diagram changes must be rendered again, and generated PNGs need to travel with the article workspace.

Those costs buy useful properties:

  • authoring Markdown remains independent of a storage provider;
  • the publisher sees only local paths it can validate;
  • a preview can exercise image replacement without a platform write;
  • the final URL artifact cannot accidentally become the next publishing input.

It is not a universal rule. A site that natively renders Mermaid could publish the fence directly. The extra render phase is justified when the target platform needs ordinary image URLs and the publishing tool owns image processing.

How I verified it

I verified the boundary at four levels:

  1. Source trace: checked that rendering writes the generated image, verifies it exists, and changes the fence into a local relative image reference.
  2. Automated test: checked the Mermaid tests that assert a stable local PNG link and preserved front matter.
  3. Article run: rendered this article and inspected the generated diagram before publishing.
  4. Publisher preview: ran the generator validator and publisher dry run, which exercise the rendered Markdown without calling a platform API.

The limitation is deliberate: a dry run proves the local transformation and payload construction, not that a remote platform will display every image as expected. That final step still needs the one authorized publication result.

Conclusion

Mermaid rendering is not merely a visual build step in this pipeline. It is the conversion from an authoring language to a local-asset contract that the publisher can validate, optimize, upload, and reproduce.

Keep the source fence, generate the local image, publish the rendered Markdown, and archive the delivery-URL result. With those boundaries in place, a diagram stays both editable for authors and concrete for the platform.

AI assistance disclosure

AI assisted with outlining and drafting. Technical claims were checked against the repository, and the article's render and dry-run path were verified locally before publication.

Top comments (0)