Mermaid.js

The tool that powers the diagrams on this wiki

Warning: This section is under construction

Overview

For this site, I author everything in MDX. Diagrams are written as regular fenced codeblocks, with mermaid as the language.

The code blocks are rendered as is, and a client-side request is made to mermaid.thekevinwang.com with the diagram payload.

The server returns a SVG image:

%% A basic example
graph LR
    A --> B

Implementation

Behind this, there is a Lambda function, running a Docker container. Here is an overview of the Dockerfile.

%% Dockerfile
stateDiagram-v2
    Dockerfile --> NODE_DEPS
    Dockerfile --> GO_BUILDER
    NODE_DEPS --> RUNNER : node_modules
    GO_BUILDER --> RUNNER : binary executable

    note left of NODE_DEPS
        This contains the Mermaid CLI node binary
    end note
    state NODE_DEPS {
        [*] --> copy_node_files : ENV PUPPETEER_SKIP_DOWNLOAD=1
        copy_node_files --> npm_install
        npm_install --> [*]
    }

    note left of GO_BUILDER
        This is a basic http server
    end note
    state GO_BUILDER {
        [*] --> copy_go_files
        copy_go_files --> go_build
        go_build --> [*]
    }

    state RUNNER {
        [*] --> apk_add_chromium:  ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
        apk_add_chromium --> copy_puppeteer_config
        copy_puppeteer_config --> copy_node_deps
        copy_node_deps --> copy_go_executable
        copy_go_executable --> copy_lambda_adapter_extension
        copy_lambda_adapter_extension --> CMD
    }

This builds an OCI image, which is hosted on AWS ECR. When Lambda uses this to run a container, it internally runs a compiled http server, and uses the Mermaid CLI binary to process incoming diagram text. It returns a simple SVG string.

The end to end flow looks like this

sequenceDiagram
    participant CL as Client
    participant CF as CloudFront
    participant AGW as API Gateway
    participant LM as Lambda

    CL->>CF: GET
    activate CF
    alt Cache Hit
        CF->>CL: Cached Response
    else Cache Miss
        CF->>+AGW: Origin Request
        AGW->>+LM: Proxy Integration
        Note over LM: Run Container via ECR image
        LM->>-AGW: (Content-Type: text/plain)
        AGW->>-CF: Origin Response
        CF->>CL: (Content-Type: image/svg+xml)
    end
    deactivate CF

Rationale

The same client-side request to generate a mermaid diagram could occur server-side, but I simply have it client-side because I want to be able to request an updated diagram SVG, in response to changes in the client’s mode value (dark/default).

Content-Type

I’ve run into some issues with the Content-Type header. If I set it to the correct value — image/svg+xml — in Lambda container code, it either gets automatically base64-encoded by API Gateway, or I get an XML error.

However, if I call the lambda function's URL directly, it just works, but I don’t get any flexiblity of CDN edge caching, or adding a fully qualified domain name.

So, my fix was to use edge compute (CloudFront functions) to modify the Content-Type header accordingly. 🧠

Appendix

Here are some syntax cheatsheets I found to be helpful:

And of course, the Mermaid live playground