All posts
AutomationAdvancedCI/CD

Batch diagram rendering & automation with Mermaid

5 min readThe MermaidCreator team

When you're managing large documentation projects, internal training materials, or auto-generated architecture reports, hand-crafting each diagram isn't scalable. Batch rendering lets you define diagram templates once and generate dozens—or hundreds—of visual outputs programmatically. This guide shows how to automate Mermaid diagram generation in your build pipeline.

When to batch-render Mermaid diagrams

Batch rendering shines in these scenarios:

  • Documentation pipelines: Generate architecture, API, or deployment diagrams for every microservice in your repo at once
  • Automated reports: Build monthly compliance or dependency reports with fresh Mermaid visuals
  • CI/CD integration: Create pull request diagrams showing deployment impacts or infrastructure changes
  • Data-driven diagrams: Render flowcharts for each feature flag, rollout plan, or release strategy
  • Accessibility-first workflows: Generate accessible SVG versions for screen readers across your entire docs site

The architecture of batch rendering

A typical pipeline has three steps:

  1. Template definition — Define reusable diagram skeletons (with placeholders)
  2. Data binding — Populate templates with dynamic content (service names, versions, metrics)
  3. Render & export — Convert bound diagrams to PNG/SVG and save to disk

Here's a conceptual flow:

flowchart LR
    Templates["Diagram Templates<br/>(with placeholders)"] --> Bind["Bind Data<br/>(JSON, CSV, or<br/>database)"]
    Services["Service Registry<br/>or Config"] --> Bind
    Bind --> Render["Render to PNG/SVG<br/>(Node.js CLI or<br/>mermaid.js)"]
    Render --> Output["Save to<br/>docs/dist/<br/>or CDN"]
    Output --> Deploy["Deploy or<br/>publish"]

Option 1: Node.js CLI rendering

The @mermaid-js/mermaid-cli package is the fastest way to batch-render Mermaid files. Install it:

npm install --save-dev @mermaid-js/mermaid-cli

Create a directory of .mermaid or .md files, then render them all to PNG:

mmdc -i diagrams/*.mermaid -o dist/ -e png

For more control, use a Node.js script:

const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');

const diagramsDir = 'diagrams/';
const outputDir = 'dist/';

fs.readdirSync(diagramsDir).forEach((file) => {
  if (file.endsWith('.mermaid')) {
    const input = path.join(diagramsDir, file);
    const output = path.join(outputDir, file.replace('.mermaid', '.png'));
    exec(`mmdc -i ${input} -o ${output} -e png`);
  }
});

Option 2: Programmatic rendering with mermaid.js

For more control over rendering, use the mermaid npm package directly (Node.js + headless browser):

import mermaid from 'mermaid';
import * as fs from 'fs';
import puppeteer from 'puppeteer';

async function renderDiagram(diagramText, outputPath) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // Set content with Mermaid diagram
  await page.setContent(`
    <html>
      <head>
        <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
      </head>
      <body>
        <div class="mermaid">${diagramText}</div>
        <script>
          mermaid.initialize({ startOnLoad: true, securityLevel: 'loose' });
          mermaid.contentLoaded();
        </script>
      </body>
    </html>
  `);
  
  // Wait for Mermaid to render
  await page.waitForSelector('svg');
  
  // Screenshot and save
  await page.screenshot({ path: outputPath, type: 'png' });
  await browser.close();
}

// Batch render all diagrams
const diagrams = [
  { text: 'flowchart LR\n  A --> B', output: 'dist/flow1.png' },
  { text: 'sequenceDiagram\n  A->>B: Hello', output: 'dist/seq1.png' },
];

for (const diagram of diagrams) {
  await renderDiagram(diagram.text, diagram.output);
  console.log(`✓ Rendered ${diagram.output}`);
}

Template-based rendering with data binding

For complex projects, define templates with placeholders and bind them to dynamic data:

Template: templates/service-diagram.mermaid

flowchart TD
    Client["Client"]
    Client -->|requests| Service["{{serviceName}}"]
    Service -->|queries| DB["{{dbType}} ({{dbName}})"]
    DB -->|replicates to| Backup["Backup: {{backupLocation}}"]
    Service -->|publishes to| Queue["{{queueType}}"]

Data file: services.json

[
  { "serviceName": "Auth Service", "dbType": "PostgreSQL", "dbName": "users_db", "backupLocation": "us-west-2", "queueType": "Redis" },
  { "serviceName": "Orders API", "dbType": "MongoDB", "dbName": "orders_db", "backupLocation": "eu-central-1", "queueType": "Kafka" }
]

Binding script:

const fs = require('fs');
const path = require('path');

const template = fs.readFileSync('templates/service-diagram.mermaid', 'utf-8');
const services = JSON.parse(fs.readFileSync('services.json', 'utf-8'));

services.forEach((service) => {
  let diagram = template;
  Object.keys(service).forEach((key) => {
    diagram = diagram.replace(`{{${key}}}`, service[key]);
  });
  
  const outputPath = `dist/${service.serviceName.replace(/\s+/g, '-')}.mermaid`;
  fs.writeFileSync(outputPath, diagram);
  console.log(`✓ Generated ${outputPath}`);
});

Integration with CI/CD pipelines

Add batch rendering to your GitHub Actions or GitLab CI workflow:

# .github/workflows/generate-diagrams.yml
name: Generate Diagrams

on:
  push:
    paths:
      - 'services.json'
      - 'templates/**'

jobs:
  render:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Render diagrams
        run: node scripts/batch-render.js
      
      - name: Commit and push
        run: |
          git add dist/
          git commit -m "docs: auto-generated diagrams"
          git push

Batch rendering best practices

  • Version your templates: Keep templates in git alongside their data files for reproducibility
  • Organize output: Use subdirectories in dist/ to organize diagrams by type (architecture, deployment, flows)
  • Cache aggressively: Skip re-renders for unchanged input; store diagram hashes or compare timestamps
  • Monitor rendering time: For large batches, consider parallelizing renders or sampling for preview
  • Test examples: Include a few hand-crafted reference diagrams to catch rendering issues early
  • Use SVG for docs: SVG diagrams scale and remain crisp; PNG works best for static exports and reports

Common pitfalls

Diagram syntax errors in data: Validate placeholder values—bad characters in node labels will break Mermaid syntax. Escape quotes and special characters:

const escapeLabel = (label) => label.replace(/"/g, '\\"').replace(/\n/g, ' ');

Browser timeout in puppeteer: Mermaid can take time to render complex diagrams. Add explicit waits:

await page.waitForSelector('svg', { timeout: 30000 }); // 30s timeout

Missing fonts in headless rendering: Headless browsers may not include all system fonts. Embed web fonts in your HTML template for consistent output.

FAQ

Can I batch-render from within CI/CD without installing a CLI? Yes—use the Node.js mermaid package directly with puppeteer in your CI runner. This avoids external CLI dependencies.

What's the performance difference: PNG vs. SVG for large batches? SVG renders 2–3× faster (no pixel rasterization), but PNG is more compatible with legacy tools and email clients. Render to both if you need flexibility.

How do I handle diagrams that exceed rendering size limits? Split large diagrams into sub-diagrams with internal links, or use Mermaid's %%{init: {maxLength: 50000}} directive to increase the limit (careful with browser memory).

Ready to scale your diagram generation? Use MermaidCreator to prototype a diagram template visually, export the Mermaid syntax, then wire it into your batch pipeline.

Related posts