---
atroot: true
template:
slug: docs
title: We rolled our own documentation site
subtitle: You don't need Mintlify
date: 2026-01-12
authors:
  - name: Akshay
    email: akshay@tangled.org
    handle: oppi.li
draft: false
---

We recently organized our documentation and put it up on
https://docs.tangled.org, using just pandoc. For several
reasons, using pandoc to roll your own static sites is more
than sufficient for small projects.

![docs.tangled.org](https://assets.tangled.network/blog/docs_homepage.png)

## requirements

- Lives in [our
  monorepo](https://tangled.org/tangled.org/core).
- No JS: a collection of pages containing just text
  should not require JS to view!
- Searchability: in practice, documentation engines that
  come bundled with a search-engine have always been lack
  lustre. I tend to Ctrl+F or use an actual search engine in
  most scenarios.
- Low complexity: building, testing, deploying should be
  easy.
- Easy to style

## evaluating the ecosystem

I took the time to evaluate several documentation engine
solutions:

- [Mintlify](https://www.mintlify.com/): It is quite obvious
  from their homepage that Mintlify is performing an AI
  pivot for the sake of doing so.
- [Docusaurus](https://docusaurus.io/): The generated
  documentation site is quite nice, but the value of pages
  being served as a full-blown React SPA is questionable.
- [MkDocs](https://www.mkdocs.org/): Works great with JS
  disabled, however the table of contents needs to be
  maintained via `mkdocs.yml`, which can be quite tedious.
- [MdBook](https://rust-lang.github.io/mdBook/index.html):
  As above, you need a `SUMMARY.md` file to control the
  table-of-contents.

MkDocs and MdBook are still on my radar however, in case we
need a bigger feature set.

## using pandoc

[pandoc](https://pandoc.org/) is a wonderfully customizable
markup converter. It provides a "chunkedhtml" output format,
which is perfect for generating documentation sites. Without
any customization,
[this](https://pandoc.org/demo/example33/) is the generated
output, for this [markdown file
input](https://pandoc.org/demo/MANUAL.txt).

- You get an autogenerated TOC based on the document layout
- Each section is turned into a page of its own

Massaging pandoc to work for us was quite straightforward:

- I first combined all our individual markdown files into
  [one big
  `DOCS.md`](https://tangled.org/tangled.org/core/blob/master/docs/DOCS.md)
  file.
- Modified the [default
  template](https://github.com/jgm/pandoc-templates/blob/master/default.chunkedhtml)
  to put the TOC on every page, to form a "sidebar", see
  [`docs/template.html`](https://tangled.org/tangled.org/core/blob/master/docs/template.html)
- Inserted tailwind `prose` classes where necessary, such
  that markdown content is rendered the same way between
  `tangled.org` and `docs.tangled.org`

Generating the docs is done with one pandoc command:

```bash
pandoc docs/DOCS.md \
    -o out/ \
    -t chunkedhtml \
    --variable toc \
    --toc-depth=2 \
    --css=docs/stylesheet.css \
    --chunk-template="%i.html" \
    --highlight-style=docs/highlight.theme \
    --template=docs/template.html
```

## avoiding javascript

The "sidebar" style table-of-contents needs to be collapsed
on mobile displays. Most of the engines I evaluated seem to
require JS to collapse and expand the sidebar, with MkDocs
being the outlier, it uses a checkbox with the
[`:checked`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:checked)
pseudo-class trick to avoid JS. 

The other ways to do this are:

- Use `<details` and `<summary>`: this is definitely a
  "hack", clicking outside the sidebar does not collapse it.
  Using Ctrl+F or "Find in page" still works through the
  details tag though.
- Use the new `popover` API: this seems like the perfect fit
  for a "sidebar" component.

The bar at the top includes a button to trigger the popover:

```html
<button popovertarget="toc-popover">Table of Contents</button>
```

And a `fixed` position div includes the TOC itself:

```html
<div id="toc-popover" popover class="fixed top-0">
  <ul>
    Quick Start
    <li>...</li>
    <li>...</li>
    <li>...</li>
  </ul>
</div>
```

The TOC is scrollable independently and can be collapsed by
clicking anywhere on the screen outside the sidebar.
Searching for content in the page via "Find in page" does
not show any results that are present in the popover
however. The collapsible TOC is only available on smaller
viewports, the TOC is not hidden on larger viewports.

## search

There is no native search on the site for now. Taking
inspiration from [https://htmx.org](https://htmx.org)'s search bar, our search
bar also simply redirects to Google:

```html
<form action="https://google.com/search">
  <input type="hidden" name="q" value="+[inurl:https://docs.tangled.org]">
  ...
</form>
```

I mentioned earlier that Ctrl+F has typically worked better
for me than, say, the search engine provided by Docusaurus.
To that end, the same docs have been exported to a ["single
page" format](https://docs.tangled.org/single-page.html), by
just removing the `chunkedhtml` related options:

```diff
 pandoc docs/DOCS.md \
     -o out/ \
-    -t chunkedhtml \
     --variable toc \
     --toc-depth=2 \
     --css=docs/stylesheet.css \
-    --chunk-template="%i.html" \
     --highlight-style=docs/highlight.theme \
     --template=docs/template.html
```

With all the content on a single page, it is trivial to
search through the entire site with the browser. If the docs
do outgrow this, I will consider other options!

## building and deploying

We use [nix](https://nixos.org) and
[colmena](https://colmena.cli.rs/) to build and deploy all
Tangled services. A nix derivation to [build the
documentation](https://tangled.org/tangled.org/core/blob/master/nix/pkgs/docs.nix)
site is written very easily with the `runCommandLocal`
helper:

```nix
runCommandLocal "docs" {} ''
    .
    .
    .
  ${pandoc}/bin/pandoc ${src}/docs/DOCS.md ...
    .
    .
    .
''
```

The NixOS machine is configured to serve the site [via
nginx](https://tangled.org/tangled.org/infra/blob/master/hosts/nixery/services/nginx.nix#L7):

```nix
services.nginx = {
  enable = true;
  virtualHosts = {
    "docs.tangled.org" = {
      root = "${tangled-pkgs.docs}";
      locations."/" = {
        tryFiles = "$uri $uri/ =404";
        index = "index.html";
      };
    };
  };
};
```

And deployed using `colmena`:

```bash
nix run nixpkgs#colmena -- apply
```

To update the site, I first run:

```bash
nix flake update tangled
```

Which bumps the `tangled` flake input, and thus
`tangled-pkgs.docs`. The above `colmena` invocation applies
the changes to the machine serving the site.

## notes

Going homegrown has made it a lot easier to style the
documentation site to match the main site. Unfortunately
there are still a few discrepancies between pandoc's
markdown rendering and
[goldmark's](https://pkg.go.dev/github.com/yuin/goldmark/)
markdown rendering (which is what we use in Tangled). We may
yet roll our own SSG,
[TigerStyle](https://tigerbeetle.com/blog/2025-02-27-why-we-designed-tigerbeetles-docs-from-scratch/)!
