Skip to content

Offline rendering & diagram backends

Diagrams, charts and maps can be rendered three different ways, and — since v2.13.0 — through two different backends. This page covers what each combination costs, and how to build a document without Chromium installed at all.

Both CLIs share these flags. Where they differ, it is called out.

--render-mode decides when interactive content is rendered, and applies to mermaid, PlantUML, charts and maps at once.

Mode What happens Needs internet at build Needs internet at view
browser (default) The HTML links CDN scripts; the reader’s browser renders No Yes
offline-assets Rendered at build time into an assets/ folder next to the output Yes No
offline-inline Rendered at build time and embedded in the HTML Yes No

browser produces the smallest file but is useless offline. offline-inline produces one self-contained file — the right choice for email, archives, or feeding the HTML to an external PDF service.

--diagram-backend selects what renders mermaid and PlantUML in the offline-* modes (and for --format pdf in slidelang).

Backend Renders mermaid/PlantUML with Chromium on the build machine
chromium (default) Headless Chrome, via chromedp Required (~180 MB)
kroki HTTP requests to a Kroki server Not required
Terminal window
doclang build report.doclang \
--render-mode offline-inline \
--diagram-backend kroki \
--kroki-server https://kroki.internal.example.com

Kroki does use Chromium internally to render mermaid — but on the server, which is the operator’s concern, not the build machine’s. That is the trade: one service the organization runs, instead of ~180 MB every user downloads. Omit --kroki-server to use the public https://kroki.io.

The CLIs decide whether to start Chromium per element type actually present in the document, not per requested mode. A document reaches zero Chromium when every one of its rich elements is covered:

Element Covered without Chromium How
PlantUML ✅ Always HTTP to a PlantUML server, no browser involved even on the default backend
Mermaid ✅ With --diagram-backend kroki HTTP to the Kroki server
Charts ✅ Conditionally — see below Rasterized natively in Go (go-analyze/charts)
Maps ❌ Never Leaflet tiles require a browser
Math ❌ Never MathJax requires a browser

A chart renders natively only when all three hold:

  1. its type is bar, line, pie or doughnut;
  2. it carries no options: block (arbitrary Chart.js config has no native equivalent, and silently approximating it would draw a different chart than the author asked for);
  3. it is not in JSON mode.

--image-format webp also disables the native path — WebP encoding goes through the browser.

Anything not covered falls back to Chromium deterministically. So:

Terminal window
# Zero Chromium: text + mermaid + a plain bar chart
doclang build report.doclang \
--render-mode offline-inline \
--diagram-backend kroki
Terminal window
# Still needs Chromium: the document contains a map
doclang build with-map.doclang \
--render-mode offline-inline \
--diagram-backend kroki # mermaid is covered, the map is not

If you want certainty rather than inference, watch the build log: doclang reports when it skips the browser for natively-rendered charts, and logs Initializing Chromium renderer... whenever it does start one. No such line means no browser was launched.

The Kroki server is read from --kroki-server, a flag, never frontmatter. This is deliberate and load-bearing.

Frontmatter is document content, and document content is attacker-controllable — a kroki_server: key would let a document redirect its own diagram traffic to a host of the attacker’s choosing. A flag has the same trust level as --theme: it comes from the operator running the build.

Consequently, the initial request to --kroki-server is not IP-restricted, so an on-prem Kroki on 10.x or 192.168.x works with no special configuration. Two restrictions do apply, and both bite in real deployments:

  • Any redirect to a private or loopback IP is rejected, even one issued by your own server. A Kroki behind a load balancer or ingress that redirects internally will fail.
  • Redirects are HTTPS-only. http://kroki.internal → https://10.0.0.5 fails on both counts.

Neither restriction is new — they are the same rules that have always governed --plantuml-server.

Requests use Kroki’s POST /{type}/{format} endpoint, sending the diagram source in the body as plain text, so diagram size is not bounded by URL length.

--plantuml-format (svg or png) is honoured by both backends, and applies to PlantUML only — mermaid is always fetched as SVG.

offline-inline requires SVG by construction: an inline diagram is embedded as markup, not as a binary blob, so PNG bytes injected inline would be binary garbage rather than a diagram. --plantuml-format png is therefore only meaningful with offline-assets.

Combining png with offline-inline is not silently downgraded to SVG — each affected diagram is replaced by a visible <div class="plantuml-error"> in the output, and the build still succeeds. PDF output is unaffected either way: both CLIs force SVG internally for --format pdf.

--image-format (png or webp) applies to charts and maps in offline modes only.

Fully offline builds need every remote dependency replaced:

Terminal window
doclang build report.doclang \
--render-mode offline-inline \
--diagram-backend kroki \
--kroki-server https://kroki.internal.example.com \
--plantuml-server https://plantuml.internal.example.com

Maps are the exception with no offline story: tiles are fetched from a remote tile server at build time, and a failure leaves a permanent <div class="map-error"> in the output. Documents intended for air-gapped builds should avoid map elements.