Netdocs

Notice anything different?

Well- this entire site is no longer built with MkDocs. It's now built with something I wrote myself, called Netdocs - a static site generator, written in .NET 11.

Why though?

Honestly? Build performance.

This blog has grown to over 200 posts. And, don't get me wrong - Material for MkDocs is genuinely one of my favorite pieces of open-source software. But, as the post count kept climbing, my local rebuilds got slow enough that it started getting in the way of me actually writing.

So, one evening, I got annoyed enough to do something about it. I wanted something that:

  • Built this site fast.
  • Ran as a single binary - no Python, Ruby, or Node toolchain to keep happy.
  • Could take my existing docs/ folder and build it with little-to-no changes.

I vibe-coded a .NET replacement, pointed it at this site, and kept poking at it until it rendered basically everything the same as before. If you're reading this, it worked.

Before you get any ideas

Netdocs is an AI-generated derivative work. It borrows from - and reuses files from - MkDocs and Material for MkDocs. All the credit for the theme, the compiled CSS/JS, the search, and the overall design goes to Martin Donath (@squidfunk) and everyone who's contributed to Material for MkDocs. Those assets are redistributed unmodified, under the MIT license.

If you want a real, supported, grown-up tool - use Material for MkDocs, and go sponsor it. Netdocs is an evenings-and-weekends project. No warranties, no promises. I have a day job, and I stay busy.

So what does it actually do?

The whole goal was to be a near drop-in replacement for my existing Material for MkDocs setup. My Markdown builds as-is. The config that used to live in mkdocs.yml now lives in the Netdocs section of a plain appsettings.json.

Here's roughly how it stacks up:

Netdocs Material for MkDocs Hugo
Runtime .NET (single binary) Python Go (single binary)
Config appsettings.json mkdocs.yml hugo.toml
Material theme Built-in Yes Bring your own
Plugins written in C# Python Go templates
Incremental build cache Yes No Yes
Parallel rendering Yes No Yes
Built-in search Yes (lunr) Yes (lunr) No

The two things that actually solved my problem: an incremental render cache (only the pages that changed get re-rendered), and parallel rendering (it uses every core, instead of one). A full rebuild of this site now takes a few seconds. Saving a single post while I'm writing is basically instant.

What's new around here

Since it's my own generator now, I added a few things I'd wanted for a while. Two you might actually run into:

Interactive calculators. I write a lot of posts about power draw, costs, and networking. I was tired of hardcoding "at $0.12/kWh, this costs…". So now I can drop a little calculator right into a post, and you can plug in your own numbers:

What's that thing costing you?
Per month
Per year

It's all client-side - type in the boxes and the numbers update. No MkDocs equivalent that I know of; it was one of the first things I built.

Other stuff that got added along the way:

  • netdocs serve - a dev server that reloads the page when I save. It's most of why writing doesn't feel like a chore anymore.
  • Built-in deploy - filesystem, git (gh-pages), and s3 publishing are just build config. No separate deploy step.
  • Offline builds - a toggle that self-hosts every CDN asset (highlight.js, Mermaid, fonts, emoji) so the whole site runs straight off file://.
  • HTML/CSS/JS minification and WebP conversion, as build toggles.
  • netdocs import - point it at an existing mkdocs.yml and it writes out the appsettings.json.

The plumbing

Under the hood it's a plugin pipeline - Markdown gets preprocessed, rendered (Markdig), dropped into templates (Scriban), and then a few things run at the end (search index, tags, RSS). Plugins are just C# classes that hook into whatever stage they care about.

If you actually want to write one, the Netdocs docs have a build-lifecycle diagram and the full list of events and callbacks - that's the right place for the deep detail, not a blog post.

What's included

Most of the Material/MkDocs plugins I actually depend on have a Netdocs version built right in:

  • snippets - reusable includes, with ${param} templating
  • blog - index, archive, and category pages
  • tags - tag pages and a tag index
  • table-reader - drop a CSV straight into a Markdown table (this is how my networking hardware tables are built)
  • rss - a feed for the blog
  • redirects - including slugify-aware ones, so old links don't rot
  • glightbox - image lightboxes
  • macros, arithmatex (math), social cards, client-side search, and a handful more.

Grabbing it

There are prebuilt binaries on the releases page - no .NET SDK needed.

If you'd rather just run it in a container:

docker run --rm -v ${PWD}:/site \
  ghcr.io/xtremeownage/netdocs build --config /site/appsettings.json

Building and serving locally looks about like you'd expect:

netdocs build --config ./appsettings.json
netdocs serve --config ./appsettings.json --port 8000

And in CI, there's a GitHub Action that grabs the native binary for you - no .NET setup step:

- uses: XtremeOwnage/Netdocs@v1
  with:
    command: build
    config: appsettings.json
    args: --prod

That last one is, in fact, exactly how this site gets deployed now.

Should you use it?

Probably not - and I mean that in the nicest way possible.

Material for MkDocs is the sensible choice, and I'd point most people there without a second thought. But if you're a .NET person who wants a single-binary generator, or you just want to dig around and see how the thing works - it's all MIT-licensed and the source is right there.

Contributions are welcome. Donations are not - a simple kudos, or a hello in my Discord, is more than enough. Just tag me, because between work and life I can be slow to respond.

And one more time, because it's worth repeating - huge thanks to @squidfunk and the Material for MkDocs project. None of this would exist without their work.