CLI reference
The netdocs executable exposes these commands: build, profile, serve, watch, new, and import.
netdocs - static site generator
Usage:
netdocs build [options] Build the site to the output directory
netdocs profile [options] Build once and print a per-phase/plugin timing tree
netdocs serve [options] Serve with live reload
netdocs watch [options] Publish daemon: poll a git remote and rebuild on push
netdocs new [path] Scaffold an annotated appsettings.json
netdocs import [mkdocs.yml] Convert an mkdocs.yml to appsettings.json
Commands
| Command | Description |
|---|---|
netdocs build |
Build the site to the configured output dir (siteDir). |
netdocs profile |
Build once (cache-bypassed) and print a tree of where build time is spent. |
netdocs serve |
Kestrel dev server with file-watch rebuilds + WebSocket live reload. |
netdocs watch |
Long-running publish daemon: polls a git remote and rebuilds when the tracked branch advances. |
netdocs new |
Scaffold a fully-annotated appsettings.json (all common options + doc links). |
netdocs import |
Convert an existing mkdocs.yml into a Netdocs appsettings.json. |
netdocs --help |
Print usage. |
netdocs new
Writes a ready-to-edit appsettings.json with every common setting, sane defaults, and inline
links back to this documentation. The file is JSONC — // comments and trailing commas are kept
and parsed at build time, so you can leave the guidance in place.
netdocs new # writes ./appsettings.json
netdocs new docs/appsettings.json
netdocs new --force # overwrite an existing file
Starting from scratch:
netdocs new
mkdir docs && echo "# Home" > docs/index.md
netdocs serve
If no command is given, build is assumed.
netdocs profile
netdocs profile runs a single, cache-bypassed build and prints a tree showing how long each
build phase took, broken down by plugin where a phase runs plugin hooks. Use it to find the
slowest part of a build — a heavy preprocessor, an expensive OnBuildComplete hook, or the
markdown/template render itself.
netdocs profile
The report lists phases biggest-first. Each line shows the elapsed time and its share of its
parent scope; nested lines are the individual plugins within that phase, and an xN suffix
counts how many times a scope ran (for example, a preprocessor invoked once per page):
Build profile (time by phase):
6. render markdown 263.4 ms 30.0%
8. template render 242.1 ms 27.6%
11. OnBuildComplete hooks 142.2 ms 16.2%
search 81.9 ms 57.7%
social 57.6 ms 40.6%
tags 2.4 ms 1.7%
5. preprocess markdown 18.0 ms 2.0%
calculator 17.6 ms 98.1% x48
...
Total measured: 877.8 ms
Because it bypasses the incremental render cache, profile always reflects a full cold build —
the numbers are comparable run to run rather than skewed by cache hits.
Options
| Option | Alias | Description |
|---|---|---|
--config <path> |
-f |
Path to appsettings.json (default ./appsettings.json). |
--port <port> |
-p |
Dev server port for serve (default 8000). |
--clean |
Remove existing output before building. | |
--no-cache |
Ignore the incremental render cache and re-render every page. | |
--strict |
Treat build warnings (including validation problems, plugin/template errors) as failures. | |
--prod |
--production |
Production build (enables prod-only plugins such as social cards). |
--verbose |
-v |
Verbose (Trace) logging. |
--remote <name> |
watch: git remote to poll (default origin). |
|
--branch <name> |
watch: branch to track (default the current branch). |
|
--interval <sec> |
watch: poll interval in seconds (default 30). |
|
--once |
watch: run a single check-and-rebuild, then exit (useful for cron/testing). |
Note
Every build writes output incrementally: files are only rewritten when their bytes change, and files no longer produced are pruned. Unchanged pages keep their previous contents and timestamps, which is what makes republishing a small change cheap.
Incremental render cache
The Markdown parse/render step is content-hash cached under .cache/render.json
(gitignored). On each build, a page's rendered HTML, title, plain text, and table of
contents are reused when its processed Markdown, the pipeline configuration, and the site
link map are all unchanged — so warm builds skip re-rendering unchanged pages.
The cache is self-invalidating: adding, removing, or renaming any page changes the link map
hash and invalidates every entry (a full re-render), which keeps cross-page links and
navigation correct. Pass --no-cache to bypass it entirely, or delete the .cache/
directory to reset it. Each build logs how many pages were reused, e.g.
Render cache: 243/243 pages reused.
Watch daemon
netdocs watch is a long-running publish daemon — distinct from serve, which watches
local files for a developer. Instead, watch polls a git remote and rebuilds the site
in place whenever the tracked branch advances, so a push (e.g. from CI or a teammate)
becomes a live update without a full CI job:
netdocs watch --remote origin --branch main --interval 30
On each poll it runs git fetch and compares your HEAD to the remote branch:
- Remote advanced (fast-forward): the working tree is advanced with
git merge --ff-onlyand the site is rebuilt. - Local ahead or diverged: the sync is skipped with a warning and no local commits are touched — the daemon never runs a destructive reset.
Every rebuild is a full, cache-accelerated build: the render cache reuses unchanged Markdown and the output writer republishes only the files whose bytes actually changed (pruning any that are no longer produced). This keeps the on-disk diff minimal for a small content change while still correctly reflecting structural changes — navigation, blog, or tag updates touch every affected page, not just one.
Use --once to run a single check-and-rebuild and exit, which is handy for a cron job or a
smoke test. The daemon requires the project root to be a git repository.
Examples
Build using an explicit config file:
netdocs build --config ./appsettings.json
Production build with verbose logging:
netdocs build --prod --verbose
Serve on a specific port:
netdocs serve --port 8080
Run the publish daemon, tracking main on origin every 15 seconds:
netdocs watch --remote origin --branch main --interval 15
Importing from MkDocs
If you already have a Material for MkDocs project, netdocs import converts its
mkdocs.yml into an equivalent Netdocs appsettings.json so you can migrate without
rewriting configuration by hand:
# Convert ./mkdocs.yml -> ./appsettings.json
netdocs import
# Convert an explicit file to a chosen output path
netdocs import path/to/mkdocs.yml --out appsettings.json
# Overwrite an existing appsettings.json
netdocs import --force
The importer maps site metadata, theme (name, palette, features, font, icon, logo,
favicon), nav (including nested sections), plugins, markdown_extensions,
extra_css, extra_javascript, and extra into the Netdocs schema. Empty and default
values are omitted. It refuses to overwrite an existing output file unless you pass
--force.
Note
Not every MkDocs plugin has a Netdocs equivalent. Review the generated
appsettings.json, remove any plugins Netdocs does not implement, and run
netdocs build to validate. See the plugins reference for the
built-in set.
Environment variables
Configuration can be overridden with environment variables prefixed NETDOCS_
(standard Microsoft.Extensions.Configuration binding). Production builds also set
MKDOCS_PROD_BUILD=true so prod-only behaviour (like social-card generation) activates.
Logging
Logging is the standard .NET Logging section of appsettings.json. Set per-category
levels, for example:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Build": "Debug",
"Netdocs": "Trace"
}
}
}
The --verbose flag forces Trace globally.