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