Overview
WitDocs is a static site framework for .NET built on Blazor WebAssembly. It follows the same general approach as Astro, Hugo, or Jekyll — content is authored in markdown, the framework renders it into a complete website — but the entire stack is C# and Blazor. No JavaScript build tooling is required.
The site you're reading right now is built with WitDocs.
The framework consists of three NuGet packages:
| Package | Description |
|---|---|
| OutWit.Web.Framework | Core framework: page components, services, CSS design system, markdown rendering |
| OutWit.Web.Generator | CLI tool: generates sitemap, RSS feed, search index, Open Graph images, static HTML for SEO |
| OutWit.Web.Templates | dotnet new project template for bootstrapping a new site |
Source code: github.com/dmitrat/WitDocs
How It Works
WitDocs is a Blazor WebAssembly application. The user interacts with a full Blazor SPA — with Blazor components, client-side routing, and interactive controls (including custom extension components embedded in content). Static HTML pages are generated at build time separately, purely for SEO — so that search engine crawlers see fully rendered content.
To keep the Blazor application responsive, the framework uses a two-tier content loading strategy:
- Navigation menus, blog cards, and project cards are pre-built into JSON indices at build time (
navigation-index.json,content-metadata.json). List pages like the home page and blog archive render instantly from these lightweight caches without touching any markdown files. - Individual content pages (a specific blog post, a doc page, a project page) load and parse their markdown on demand when the user navigates to them.
This means the site feels fast on initial load and navigation, while individual pages fetch only the content they need.
Content Model
Content is organized as markdown files with YAML frontmatter in the wwwroot/content/ directory:
wwwroot/content/
blog/
2025-01-15-my-first-post.md
projects/
01-my-project/index.md
docs/
getting-started.md
articles/
architecture-overview.md
Each file includes metadata in the frontmatter:
---
title: 'Getting Started'
description: 'How to set up a WitDocs site from scratch.'
tags: [tutorial, getting-started]
publishDate: 2025-01-15
menuTitle: 'Getting Started'
showInMenu: true
---
Content in standard markdown...
The framework supports five built-in content types — blog posts, projects, articles, docs, and features — plus user-defined custom sections.
Page Components
WitDocs provides a complete set of Blazor page components. Razor pages act as thin route wrappers:
@page "/blog/{Slug}"
<BlogPostPage Slug="@Slug" />
| Component | Route | Description |
|---|---|---|
HomePage |
/ |
Hero section, featured projects grid |
BlogListPage |
/blog |
Blog listing with tag filtering and search |
BlogPostPage |
/blog/{slug} |
Blog post with reading time estimate |
ProjectPage |
/project/{slug} |
Project detail page |
DocsPage |
/docs/{slug} |
Documentation with sidebar navigation and prev/next links |
ArticlePage |
/article/{slug} |
Article with auto-generated table of contents |
SearchPage |
/search |
Full-text client-side search |
ContactPage |
/contact |
Contact form with Cloudflare Turnstile support |
Each component handles SEO metadata (Open Graph, Twitter Cards, JSON-LD) automatically.
Configuration
Site structure is defined in a single site.config.json:
{
"siteName": "My Project",
"baseUrl": "https://myproject.io",
"defaultTheme": "dark",
"navigation": [
{ "title": "Home", "href": "/" },
{ "title": "Docs", "href": "/docs" },
{ "title": "Blog", "href": "/blog" }
],
"footer": {
"copyright": "Author Name",
"socialLinks": [
{ "platform": "github", "url": "https://github.com/..." }
]
}
}
Navigation menus are populated automatically from content files — adding a new doc or project makes it appear in the corresponding dropdown without any manual routing.
Theming
Theming is done through CSS variables in theme.css. Override a set of color variables and every component adapts:
:root {
--color-accent: #007CF0;
--color-background: #ffffff;
--color-text-primary: #333333;
}
[data-theme="dark"] {
--color-accent: #00a3ff;
--color-background: #1f1f1f;
--color-text-primary: #d1d1d1;
}
Light/dark mode toggle is built in and persists across sessions.
Custom Content Sections
Beyond the built-in content types, custom sections can be defined in site.config.json:
{
"contentSections": [
{ "folder": "tutorials", "route": "tutorials", "menuTitle": "Tutorials", "type": "article" },
{ "folder": "api-reference", "route": "api", "menuTitle": "API Reference", "type": "doc" }
]
}
Each section gets its own URL prefix, navigation menu entries, and rendering behavior — article-style (with table of contents) or doc-style (with prev/next navigation).
Extensibility
Custom Blazor components can be embedded directly in markdown content:
Here's a demo video:
[[YouTube Id="dQw4w9WgXcQ"]]
Architecture diagram:
[[Svg Src="./architecture.svg" Alt="System Architecture"]]
YouTube and Svg are included out of the box. Since the user sees a live Blazor application (not static HTML), embedded components are fully interactive Blazor controls with their own state, parameters, and behavior.
Adding a custom component involves implementing a Blazor component and registering it in the ComponentRegistry:
public class ComponentRegistry
{
public ComponentRegistry()
{
Register("YouTube", typeof(YouTube));
Register("Svg", typeof(Svg));
// Register your own:
// Register("CodeSandbox", typeof(CodeSandbox));
}
}
After registration, the component becomes available in all markdown content via the [[ComponentName Param="value"]] syntax.
Build-Time Generation
On Release build, the OutWit.Web.Generator CLI tool produces:
| Generated File | Purpose |
|---|---|
navigation-index.json |
Pre-built navigation menus (fast header rendering) |
content-metadata.json |
Blog/project/article cards (fast list page rendering) |
search-index.json |
Pre-built full-text search index |
content/index.json |
Content manifest |
sitemap.xml |
SEO sitemap with lastmod dates |
robots.txt |
Crawler rules |
feed.xml |
RSS feed for blog posts |
*/index.html |
Static HTML pages for search engine crawlers |
_headers, _routes.json |
Hosting provider configuration |
The JSON indices serve the Blazor application (fast rendering of lists and navigation). The static HTML pages serve search engines (SEO). Both are generated from the same markdown source.
Generation is triggered automatically:
dotnet build -c Release
Or manually via CLI:
outwit-generate --content-path ./wwwroot/content --output-path ./wwwroot --site-url https://example.com
Deployment
The output is a standard static wwwroot/ directory. Built-in hosting provider support includes Cloudflare Pages, Netlify, Vercel, and GitHub Pages — each generates the appropriate configuration files (_headers, _redirects, vercel.json, .nojekyll).
A typical CI/CD step:
- name: Build
run: dotnet publish -c Release -o publish
- name: Deploy
uses: cloudflare/pages-action@v1
with:
directory: publish/wwwroot
Quick Start
# Install the template
dotnet new install OutWit.Web.Templates
# Create a new site
dotnet new outwit-web -n MySite --site-name "My Site" --base-url "https://mysite.com"
# Run in development
cd MySite
dotnet run
Template options include --accent-color, --hosting-provider, --include-docs-section, --include-blog-section, and others.
Learn More
- Documentation: witdocs.io
- Source code: github.com/dmitrat/WitDocs
- License: Apache 2.0