This documentation is also published as Markdown for efficient machine reading: the whole site is indexed at /llms.txt, and every page has a clean Markdown copy under /_llms/. These are generated from the same source and cost far fewer tokens to read than this rendered HTML.

Skip to main content Skip to navigation
Getting Started

Scaffold a blog with BlogSite

Swap the bare Pennington host for the BlogSite template and configure the core options that drive the home, archive, post, tag, and RSS routes.

By the end of this tutorial, a running BlogSite host titled "Scaffold Blog" serves a home listing, /archive, /blog/<slug>/, /tags/, /tags/<name>/, and /rss.xml — all from a single placeholder post under Content/Blog/.

AddBlogSite folds the host, layout, navigation, and styling into one call, configured for a site where the blog is the site; for what the template wires, where the wiring stops, and why DocSite and BlogSite can't share an app, read what the templates wire for you first.

Prerequisites

The stable .NET 10 SDK is all BlogSite needs — its package targets .NET 10, and you never write the union keyword that would call for a preview SDK. See the SDK and the union shim for when the .NET 11 beta is worth opting into.

The finished code for this tutorial lives in examples/BlogSiteScaffoldExample.


1. Start from the bare Pennington host

The host you built in the getting-started tutorials calls AddPennington, registers content with AddMarkdownContent<DocFrontMatter>, mounts UsePennington, and routes through a Blazor @page "/{*Path}" catch-all (MarkdownPage.razor) that resolves each URL with IPageResolver to serve individual pages.

That host serves individual pages but nothing else: no home listing, no /archive, no /blog/<slug> pages, no /tags listings, no /rss.xml feed, and no MonorailCSS chrome. The next section brings all of that in with a single AddBlogSite call.

1

Add the first post

Posts live under {ContentRootPath}/{BlogContentPath}Content/Blog/ with the defaults. Add one placeholder post so the host has something to serve. It uses four front-matter keys — title, description, date, and author — the minimum the home listing and RSS feed will need once the template is wired:

markdown
---
title: Hello world
description: A placeholder post so the scaffold has something to render. Tutorial 1.3.20 teaches the real BlogSiteFrontMatter fields.
date: 2026-04-13
author: Author Name
tags:
  - scaffold
---
  
This post exists so the bare BlogSite scaffold has at least one entry on the
home page and in the RSS feed. The next tutorial, **Author your first post
with `BlogSiteFrontMatter`**, walks through the full set of post front-matter
fields (tags, series, repository, section, redirectUrl, and more).

Checkpoint

  • Run dotnet run and visit /blog/hello-world at the URL the console prints — the page shows unstyled HTML for the markdown. None of the BlogSite chrome (home listing, archive, tag pages, RSS) exists yet.

2. Wire AddBlogSite, UseBlogSite, and RunBlogSiteAsync

AddBlogSite is the BlogSite template's single registration call; it stands in for AddPennington plus the AddMarkdownContent<DocFrontMatter> line, wiring Pennington core, MonorailCSS, the Razor chrome, and the blog content services in one call. UseBlogSite mounts the middleware stack and Razor component routes; RunBlogSiteAsync dispatches between dev-serve and static-build.

1

Replace Program.cs with the BlogSite calls

csharp
var builder = WebApplication.CreateBuilder(args);
  
builder.Services.AddBlogSite(() => new BlogSiteOptions
{
    SiteTitle = "Scaffold Blog",
    SiteDescription = "A minimal BlogSite scaffold showing AddBlogSite, UseBlogSite, and RunBlogSiteAsync.",
    CanonicalBaseUrl = "https://example.com",
  
    ContentRootPath = "Content",
    BlogContentPath = "Blog",
    BlogBaseUrl = "/blog",
  
    AuthorName = "Author Name",
    AuthorBio = "Writing about software, tools, and the occasional side project.",
});
  
var app = builder.Build();
  
app.UseBlogSite(); 
  
await app.RunBlogSiteAsync(args); 

The options populated here cover site identity (SiteTitle, SiteDescription, CanonicalBaseUrl), content paths shown at their defaults (ContentRootPath, BlogContentPath, BlogBaseUrl), and author fallbacks (AuthorName, AuthorBio). The full surface lives in Pennington.BlogSite.BlogSiteOptions.

Checkpoint

  • Run dotnet run and visit / at the URL the console prints
  • The BlogSite home layout appears: site title "Scaffold Blog", a recent-posts list with one entry, header chrome, and MonorailCSS styling

3. Verify every built-in route

With the post from section 1 in place and AddBlogSite wired, every route the template ships now responds. The placeholder post carries tags: [scaffold], so the tag routes have one entry to list.

1

Walk the page routes

With the host running, visit each of these at the URL the console prints. Every one returns 200:

  • / — home listing with hello-world as the only recent post.
  • /archive — full archive, same single post in reverse-chronological order.
  • /blog/hello-world — the post itself, now rendered with BlogSite chrome.
  • /tags — the tag index, showing scaffold with a count of one.
  • /tags/scaffold — the per-tag listing with the one post.
2

Check the RSS feed

Visit /rss.xml. It returns application/rss+xml with one <item> carrying the post title, link, description, pub date, and author.

The full route surface, including the paginated /archive/page/{n} and per-tag pages, is cataloged in Built-in BlogSite routes. The next tutorial expands the post to the full BlogSiteFrontMatter surface, adding tags, series, repository, sectionLabel, and redirectUrl.

Checkpoint

  • Each page route above returns 200 and renders the placeholder post's metadata
  • /rss.xml returns application/rss+xml content with one item whose <guid> matches the canonical post URL

Summary

  • The bare AddPennington host gave way to AddBlogSite + UseBlogSite + RunBlogSiteAsync, and the full BlogSite chrome now renders.
  • The core BlogSiteOptions surface — SiteTitle, SiteDescription, CanonicalBaseUrl, ContentRootPath, BlogContentPath, BlogBaseUrl, AuthorName, AuthorBio — is populated, and each field flows through to the rendered output.
  • BlogSite binds posts through AddMarkdownContent<BlogSiteFrontMatter> (introduced in the next tutorial) and defaults content paths to Content/Blog served at /blog, which distinguishes it from the DocSite template's area-driven layout.
  • Every built-in route the template ships responds: /, /archive, /blog/<slug>, /tags, /tags/<name>, and /rss.xml.