Migrating from MkDocs
Netdocs is a drop-in-minded successor to a Material for MkDocs project: it reads the same
Markdown, ships equivalents for the common plugins, and can convert your existing
mkdocs.yml for you. This guide walks a real migration — the
XtremeOwnage blog, which this project was built to
replace — from an mkdocs-container CI pipeline to Netdocs.
1. Convert the configuration
Run the built-in importer against your existing mkdocs.yml to generate an
appsettings.json:
netdocs import mkdocs.yml --out appsettings.json
The importer maps site metadata, theme, nav (including nested sections), plugins,
markdown_extensions, extra_css, extra_javascript, and extra. See the
import command reference for details.
Review the generated file and remove any plugins that don't have a Netdocs equivalent, then build locally to validate:
netdocs build --config appsettings.json
2. Map environment-driven behavior
The MkDocs blog toggled behavior with MKDOCS_* environment variables (prod builds,
file-filtering, macros, nav pruning). In Netdocs these become explicit config plus the
--prod flag:
| MkDocs env var | Netdocs equivalent |
|---|---|
MKDOCS_PROD_BUILD=true |
netdocs build --prod (enables prod-only plugins like social cards) |
MKDOCS_FILE_FILTER=true |
file-filter plugin in plugins |
MKDOCS_MACROS_ENABLED=true |
macros plugin in plugins |
SITE_NAME, REPO_URL, REPO_NAME |
siteName, repoUrl, repoName (or NETDOCS_-prefixed env vars) |
Any config value can still be overridden at build time with a NETDOCS_-prefixed
environment variable, e.g. NETDOCS_SITENAME.
3. Replace the CI workflow
The XO blog previously built inside an mkdocs container and deployed with
mkdocs gh-deploy. Two drop-in replacements are available.
Option A — Netdocs Docker image (closest to the old pipeline)
The published GHCR image bundles the CLI, so the workflow barely changes —
swap the mkdocs image for ghcr.io/xtremeownage/netdocs and the build step for
netdocs build + git deploy:
name: Build Docs On Tag Release
on:
workflow_dispatch:
release:
types: [published]
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/xtremeownage/netdocs:latest
env:
SITE_NAME: My Documentation
NETDOCS_REPONAME: ${{ github.repository }}
NETDOCS_REPOURL: ${{ github.server_url }}/${{ github.repository }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # git-revision-date needs full history
- name: Build and deploy
run: netdocs build --prod --config appsettings.json
Configure the deploy section in appsettings.json to publish the built site to the
gh-pages branch (mirrors mkdocs gh-deploy):
"deploy": {
"target": "gh-pages",
"branch": "gh-pages",
"remote": "origin",
"message": "Deploy docs",
"push": true
}
For the DEV workflow (push to non-main branches), drop --prod and point the deploy
branch at gh-pages-dev.
Option B — setup-dotnet (no container)
If you'd rather not use a container, install the runtime and run the CLI directly — this matches the workflow that builds this documentation site:
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Build docs
run: dotnet run --project src/Netdocs.Cli -c Release -- build --prod --config appsettings.json
End users who aren't building from source should instead download a
release binary or use the Docker image rather than
running dotnet.
4. Redirects for moved pages
If your mkdocs site used the redirects
plugin, its redirect_maps are carried over automatically by netdocs import — the
generated appsettings.json keeps the same redirects plugin entry, and Netdocs' built-in
redirects plugin emits the same client-side redirect stubs at build time:
{
"Netdocs": {
"plugins": [
{
"name": "redirects",
"options": {
"redirect_maps": {
"old/page.md": "new/page/",
"gone.md": "https://example.com/moved"
}
}
}
]
}
}
Netdocs does not try to guess which URLs changed during the migration and invent
redirects for them — automatic slug diffing is unreliable and can produce redirect loops.
If your slug settings differ from mkdocs (see Slugify),
build both generators once, diff the emitted paths, and add any changed URLs to
redirect_maps explicitly. This keeps redirect behavior predictable and reviewable.
5. Verify parity
Before switching DNS/Pages over, build both generators and diff the output:
- Page count and nav match (
Built <N> pagesshould be close to your mkdocs page count). - Search, tags, blog, and social cards render as expected.
- Internal links resolve under your Pages base path (Netdocs rewrites links to be base-path safe automatically).
See the benchmarks for the performance you can expect after the switch, and the plugins reference for the built-in plugin set.