···1+---
2+atroot: true
3+template:
4+slug: docs
5+title: why we rolled our own documentation site
6+subtitle: you don't need mintlify
7+date: 2026-01-06
8+authors:
9+ - name: Akshay
10+ email: akshay@tangled.org
11+ handle: oppi.li
12+draft: true
13+---
14+15+We recently organized our documentation and put it up on
16+https://docs.tangled.org, using just pandoc. For several
17+reasons, using pandoc to roll your own static sites is more
18+than sufficient for small projects.
19+20+
21+22+## requirements
23+24+- Lives in [our
25+ monorepo](https://tangled.org/tangled.org/core).
26+- No JS: a collection of pages containing just text
27+ should not require JS to view!
28+- Searchability: in practice, documentation engines that
29+ come bundled with a search-engine have always been lack
30+ lustre. I tend to Ctrl+F or use an actual search engine in
31+ most scenarios.
32+- Low complexity: building, testing, deploying should be
33+ easy.
34+- Easy to style
35+36+## evaluating the ecosystem
37+38+I took the time to evaluate several documentation engine
39+solutions:
40+41+- [Mintlify](https://www.mintlify.com/): It is quite obvious
42+ from their homepage that mintlify is performing an AI
43+ pivot for the sake of doing so.
44+- [Docusaurus](https://docusaurus.io/): The generated
45+ documentation site is quite nice, but the value of pages
46+ being served as a full-blown React SPA is questionable.
47+- [MkDocs](https://www.mkdocs.org/): Works great with JS
48+ disabled, however the table of contents needs to be
49+ maintained via `mkdocs.yml`, which can be quite tedious.
50+- [MdBook](https://rust-lang.github.io/mdBook/index.html):
51+ As above, you need a `SUMMARY.md` file to control the
52+ table-of-contents.
53+54+MkDocs and MdBook are still on my radar however, in case we
55+need a bigger feature set.
56+57+## using pandoc
58+59+[pandoc](https://pandoc.org/) is a wonderfully customizable
60+markup converter. It provides a "chunkedhtml" output format,
61+which is perfect for generating documentation sites. Without
62+any customization,
63+[this](https://pandoc.org/demo/example33/) is the generated
64+output, for this [markdown file
65+input](https://pandoc.org/demo/MANUAL.txt).
66+67+- You get an autogenerated TOC based on the document layout
68+- Each section is turned into a page of its own
69+70+Massaging pandoc to work for us was quite straightforward:
71+72+- I first combined all our individual markdown files into
73+ [one big
74+ `DOCS.md`](https://tangled.org/tangled.org/core/blob/master/docs/DOCS.md)
75+ file.
76+- Modified the [default
77+ template](https://github.com/jgm/pandoc-templates/blob/master/default.chunkedhtml)
78+ to put the TOC on every page, to form a "sidebar", see
79+ [`docs/template.html`](https://tangled.org/tangled.org/core/blob/master/docs/template.html)
80+- Inserted tailwind `prose` classes where necessary, such
81+ that markdown content is rendered the same way between
82+ `tangled.org` and `docs.tangled.org`
83+84+Generating the docs is done with one pandoc command:
85+86+```bash
87+pandoc docs/DOCS.md \
88+ -o out/ \
89+ -t chunkedhtml \
90+ --variable toc \
91+ --toc-depth=2 \
92+ --css=docs/stylesheet.css \
93+ --chunk-template="%i.html" \
94+ --highlight-style=docs/highlight.theme \
95+ --template=docs/template.html
96+```
97+98+## avoiding javascript
99+100+The "sidebar" style table-of-contents needs to be collapsed
101+on mobile displays. Most of the engines I evaluated seem to
102+require JS to collapse and expand the sidebar, with MkDocs
103+being the outlier, it uses a checkbox with the
104+[`:checked`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:checked)
105+pseudo-class trick to avoid JS.
106+107+The other ways to do this are:
108+109+- Use `<details` and `<summary>`: this is definitely a
110+ "hack", clicking outside the sidebar does not collapse it.
111+ Using Ctrl+F or "Find in page" still works through the
112+ details tag though.
113+- Use the new `popover` API: this seems like the perfect fit
114+ for a "sidebar" component.
115+116+The bar at the top includes a button to trigger the popover:
117+118+```html
119+<button popovertarget="toc-popover">Table of Contents</button>
120+```
121+122+And a `fixed` position div includes the TOC itself:
123+124+```html
125+<div id="toc-popover" popover class="fixed top-0">
126+ <ul>
127+ Quick Start
128+ <li>...</li>
129+ <li>...</li>
130+ <li>...</li>
131+ </ul>
132+</div>
133+```
134+135+The TOC is scrollable independently and can be collapsed by
136+clicking anywhere on the screen outside the sidebar.
137+Searching for content in the page via "Find in page" does
138+not show any results that are present in the popover
139+however. The collapsible TOC is only available on smaller
140+viewports, the TOC is not hidden on larger viewports.
141+142+## search
143+144+There is native search on the site for now. Taking
145+inspiration from https://htmx.org's search bar, our search
146+bar also simply redirects to Google:
147+148+```
149+<form action="https://google.com/search">
150+ <input type="hidden" name="q" value="+[inurl:https://docs.tangled.org]">
151+ ...
152+</form>
153+```
154+155+I mentioned earlier that Ctrl+F has typically worked better
156+for me than, say, the search engine provided by Docusaurus.
157+To that end, the same docs have been exported to a ["single
158+page" format](https://docs.tangled.org/single-page.html), by
159+just removing the `chunkedhtml` related options:
160+161+```diff
162+ pandoc docs/DOCS.md \
163+ -o out/ \
164+- -t chunkedhtml \
165+ --variable toc \
166+ --toc-depth=2 \
167+ --css=docs/stylesheet.css \
168+- --chunk-template="%i.html" \
169+ --highlight-style=docs/highlight.theme \
170+ --template=docs/template.html
171+```
172+173+With all the content on a single page, it is trivial to
174+search through the entire site with the browser. If the docs
175+do outgrow this, I will consider other options!
176+177+## building and deploying
178+179+We use [nix](https://nixos.org) and
180+[colmena](https://colmena.cli.rs/) to build and deploy all
181+Tangled services. A nix derivation to [build the
182+documentation](https://tangled.org/tangled.org/core/blob/master/nix/pkgs/docs.nix)
183+site is written very easily with the `runCommandLocal`
184+helper:
185+186+```nix
187+runCommandLocal "docs" {} ''
188+ .
189+ .
190+ .
191+ ${pandoc}/bin/pandoc ${src}/docs/DOCS.md ...
192+ .
193+ .
194+ .
195+''
196+```
197+198+The nixos machine is configured to serve the site [via
199+nginx](https://tangled.org/tangled.org/infra/blob/master/hosts/nixery/services/nginx.nix#L7):
200+201+```nix
202+services.nginx = {
203+ enable = true;
204+ virtualHosts = {
205+ "docs.tangled.org" = {
206+ root = "${tangled-pkgs.docs}";
207+ locations."/" = {
208+ tryFiles = "$uri $uri/ =404";
209+ index = "index.html";
210+ };
211+ };
212+ };
213+};
214+```
215+216+And deployed using `colmena`:
217+218+```bash
219+nix run nixpkgs#colmena -- apply
220+```
221+222+To update the site, I first run:
223+224+```bash
225+nix flake update tangled
226+```
227+228+Which bumps the `tangled` flake input, and thus
229+`tangled-pkgs.docs`. The above `colmena` invocation applies
230+the changes to the machine serving the site.
231+232+## notes
233+234+Going homegrown has made it a lot easier to style the
235+documentation site to match the main site. Unfortunately
236+there are still a few discrepancies between pandoc's
237+markdown rendering and
238+[goldmark's](https://pkg.go.dev/github.com/yuin/goldmark/)
239+markdown rendering (which is what we use in Tangled). We may
240+yet roll our own SSG,
241+[TigerStyle](https://tigerbeetle.com/blog/2025-02-27-why-we-designed-tigerbeetles-docs-from-scratch/)!