# Pennington > A content engine for .NET that transforms markdown into static documentation sites and blogs. Pennington is a library for building content-driven .NET websites. It provides a pipeline that discovers markdown files, parses YAML front matter, renders HTML with syntax highlighting, and generates static sites. Key features include hot reload during development, SPA-style navigation, pluggable code highlighting (including Roslyn-powered semantic highlighting), and utility-first CSS via MonorailCSS. site: https://usepennington.github.io/pennington/ canonical: https://usepennington.github.io/pennington/llms.txt generated: 2026-04-29T15:29:46.5261249Z penningtonVersion: 0.1.0-alpha.0.55 ## Map - [Reference](/reference/llms.txt) (11 entries, ~22k tokens) — API surface, host extensions, front-matter keys, Markdig extensions, UI components, diagnostics codes. - [API reference](/reference/api/llms.txt) (251 entries, ~64k tokens) — Type and member reference for this library. - [Index](https://usepennington.github.io/pennington/_llms/index.md) ## Tutorials ### Getting Started - [Create your first Pennington site](https://usepennington.github.io/pennington/_llms/tutorials/getting-started/first-site.md): Stand up a minimal ASP.NET host that serves a single markdown page through the Pennington content pipeline. - [Add your first markdown page](https://usepennington.github.io/pennington/_llms/tutorials/getting-started/first-page.md): Write front-matter-driven markdown files and watch Pennington turn them into URLs and navigation on its own. - [Style the site with MonorailCSS](https://usepennington.github.io/pennington/_llms/tutorials/getting-started/styling.md): Wire up MonorailCSS, pick a color scheme, and watch Pennington regenerate the stylesheet as you add utility classes. ### Docsite - [Scaffold a documentation site with DocSite](https://usepennington.github.io/pennington/_llms/tutorials/docsite/scaffold.md): Swap the bare Pennington host for the DocSite template and map content areas to top-level folders. - [Author a documentation page with DocFrontMatter](https://usepennington.github.io/pennington/_llms/tutorials/docsite/first-doc-page.md): Write a DocSite page end-to-end: populate DocSiteFrontMatter, add a GitHub-style alert, and group code samples into a tabbed block with a live outline nav. - [Organize content with sections and areas](https://usepennington.github.io/pennington/_llms/tutorials/docsite/sections-and-areas.md): Split a DocSite's Content/ folder into two top-level areas with subfolder-driven sections, and use staggered order: values so the sidebar groups in the order you expect. ### Blogsite - [Scaffold a blog with BlogSite](https://usepennington.github.io/pennington/_llms/tutorials/blogsite/scaffold.md): Swap the bare Pennington host for the BlogSite template and configure the core options that drive the home, archive, post, tag, and RSS routes. - [Author your first post with BlogSiteFrontMatter](https://usepennington.github.io/pennington/_llms/tutorials/blogsite/first-post.md): Replace the scaffold's placeholder post with a fully-populated BlogSiteFrontMatter block and watch it flow into the blog index, per-tag pages, and the built-in RSS feed. - [Add a hero, projects, and social links](https://usepennington.github.io/pennington/_llms/tutorials/blogsite/hero-projects-socials.md): Populate the four BlogSite homepage surfaces — hero block, My Work card, social-icon row, and top-nav links — on BlogSiteOptions. ### Beyond Basics - [Add a second locale to your site](https://usepennington.github.io/pennington/_llms/tutorials/beyond-basics/add-a-locale.md): Turn a single-language DocSite into a bilingual one by registering a second locale, translating three pages, and letting the built-in LanguageSwitcher appear in the header. - [Connect to a Roslyn solution for live API snippets](https://usepennington.github.io/pennington/_llms/tutorials/beyond-basics/connect-roslyn.md): Point Pennington at a .sln, pull method and class source into markdown with xmldocid fences, and watch hot reload refresh snippets when the source changes. - [Author a custom Razor component for markdown](https://usepennington.github.io/pennington/_llms/tutorials/beyond-basics/custom-razor-component.md): Author a PricingCard Razor component inside a DocSite, register it with AddMdazorComponent(), and render it from a markdown page with two parameter sets. ## How To ### Content Authoring - [Work with front matter](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/front-matter.md): Declare YAML front matter, pick a built-in record, or define your own for custom keys. - [Mark drafts, tag pages, and control sort order](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/drafts-tags-ordering.md): Hide unfinished pages, attach grouping keywords, and choose where a page lands in its sidebar section using three front-matter keys. - [Reorder, rename, or hide entries in the sidebar](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/customize-sidebar.md): Use front-matter keys and folder layout to control the auto-built sidebar — reorder siblings, promote a section landing, override the section header, and hide drafts. - [Place images alongside the markdown that uses them](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/images-and-assets.md): Colocate page-specific images next to their markdown, and put shared images in wwwroot for one canonical URL. - [Group adjacent code fences into a tabbed sample](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/tabbed-code.md): Collapse adjacent fenced code blocks into one tabbed widget and customise the rendered CSS class names. - [Highlight, diff, focus, or flag lines inside a code block](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/code-annotations.md): Apply highlight, diff, focus, and error/warning line classes to fenced code with trailing `[!code ...]` comment directives. - [Add a coloured callout for a note, tip, warning, or caution](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/alerts.md): GitHub-style alerts: open a blockquote whose first line is `[!KIND]` in uppercase. Pennington recognises five kinds and paints each one differently. - [Embed a Mermaid diagram in a markdown page](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/diagrams.md): Author Mermaid diagrams in markdown with a fenced `mermaid` block and let the DocSite render them client-side with theme awareness. - [Drop a Razor component into a markdown page](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/ui-components-in-markdown.md): Embed Pennington.UI components (and your own Razor components) inside a `.md` file through Mdazor-backed rendering. - [Cross-reference pages by uid](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/cross-references.md): Give a page a stable `uid:` and link to it with `` or `[text](xref:uid)` so links survive renames and moves. - [Link between pages without hardcoding URLs](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/linking.md): Pick the right link form for sibling pages, cross-area targets, anchors, assets, and external sites — and let Pennington rewrite for sub-path deployments. - [Configure redirects](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/redirects.md): Set redirectUrl in DocSiteFrontMatter to emit a meta-refresh stub page that sends visitors to a new URL. - [Embed focused code samples](https://usepennington.github.io/pennington/_llms/how-to/content-authoring/focused-code-samples.md): Scope xmldocid fences to one member, strip declaration noise with bodyonly, and refactor long methods into named helpers so each section of a walkthrough shows one idea. ### Configuration - [Use multiple content sources](https://usepennington.github.io/pennington/_llms/how-to/configuration/multiple-sources.md): Register more than one markdown root — either as DocSite areas or as chained AddMarkdownContent calls on a bare Pennington host — and keep them from overlapping. - [Tune what the search box returns](https://usepennington.github.io/pennington/_llms/how-to/configuration/search.md): Exclude pages from the index, weight document priority, and scope the indexed HTML region without replacing the search backend. - [Recolor the site](https://usepennington.github.io/pennington/_llms/how-to/configuration/monorail-css.md): Swap palettes, override syntax-highlight colors, append site-wide rules, and tweak prose through MonorailCSS without leaving DocSite or BlogSite. - [Switch the body and heading typeface](https://usepennington.github.io/pennington/_llms/how-to/configuration/fonts.md): Drop self-hosted woff2 files into wwwroot, register @font-face rules, declare preload hints, and point DisplayFontFamily and BodyFontFamily at the new faces. - [Serve the site in multiple languages](https://usepennington.github.io/pennington/_llms/how-to/configuration/localization.md): Populate LocalizationOptions, lay out translated content in locale subdirectories, register UI translations, and wire the locale-routing middleware. - [Make the site discoverable to LLM crawlers](https://usepennington.github.io/pennington/_llms/how-to/configuration/llms-txt.md): Expose a stripped-markdown /llms.txt index plus per-page sidecars so LLM crawlers and agents can ingest your site without scraping HTML. - [Publish an RSS feed](https://usepennington.github.io/pennington/_llms/how-to/configuration/rss.md): Confirm /rss.xml is on, give every post a date so it appears in the channel, and point CanonicalBaseUrl at your production origin so links resolve. - [Publish a sitemap](https://usepennington.github.io/pennington/_llms/how-to/configuration/sitemap.md): Expose an auto-built /sitemap.xml that enumerates every canonical URL, skips drafts and redirects, and uses front-matter dates for lastmod. - [Wire the blog homepage hero](https://usepennington.github.io/pennington/_llms/how-to/configuration/blogsite-homepage.md): Populate the four BlogSite homepage surfaces — HeroContent, MyWork, Socials, and MainSiteLinks — on BlogSiteOptions in one pass. ### Extensibility - [Source content from outside the file system](https://usepennington.github.io/pennington/_llms/how-to/extensibility/custom-content-service.md): Implement IContentService to surface JSON files, a database table, or a remote API as routed pages, navigation entries, search documents, and xref targets. - [Add a custom fence syntax](https://usepennington.github.io/pennington/_llms/how-to/extensibility/code-block-preprocessor.md): Implement ICodeBlockPreprocessor to claim a fence language or :modifier suffix and return pre-rendered HTML before Pennington's default highlighter chain runs. - [Add a custom syntax highlighter](https://usepennington.github.io/pennington/_llms/how-to/extensibility/custom-highlighter.md): Implement ICodeHighlighter for a new language, claim it via SupportedLanguages, tune priority, and register with HighlightingOptions.AddHighlighter. - [Inject HTML before on every page](https://usepennington.github.io/pennington/_llms/how-to/extensibility/response-processor.md): Implement IResponseProcessor to splice a feedback widget, banner, or analytics tag before on every rendered HTML page. - [Rewrite HTML attributes after parsing](https://usepennington.github.io/pennington/_llms/how-to/extensibility/html-rewriter.md): Implement IHtmlResponseRewriter to mutate already-parsed HTML — lowercase anchors, normalise hrefs, stamp rel=noopener — without reparsing the document by hand. - [Hydrate a Razor component as a client island](https://usepennington.github.io/pennington/_llms/how-to/extensibility/island-renderer.md): Subclass RazorIslandRenderer to server-render a Razor component into a data-spa-island slot and re-render it from JSON on SPA navigation. - [Replace the docsite header or footer](https://usepennington.github.io/pennington/_llms/how-to/extensibility/override-docsite-components.md): Use DocSiteOptions to inject head content, append CSS, replace the header/footer HTML, and route extra @page components without forking the template. - [Render a Razor component as a page on a bare host](https://usepennington.github.io/pennington/_llms/how-to/extensibility/razor-page-on-bare-host.md): Use HtmlRenderer.RenderComponentAsync inside a MapGet to make a Razor component the entire response body, no DocSite layout pipeline required. - [Auto-generate an API reference tree for a class library](https://usepennington.github.io/pennington/_llms/how-to/extensibility/auto-api-reference.md): Wire a metadata backend (Roslyn workspace or a compiled .dll + .xml pair), call AddApiReference, and get one /reference/api/{type}/ page per public type plus inline Mdazor components for member tables, summaries, and extension-method catalogs. - [Emit generated output artifacts](https://usepennington.github.io/pennington/_llms/how-to/extensibility/emit-generated-artifacts.md): Implement an IContentService whose only job is to write a byte artifact to the output — robots.txt, search-index sidecars, social images — with no pages, TOC entries, or xrefs. ### Deployment - [Build a static site](https://usepennington.github.io/pennington/_llms/how-to/deployment/static-build.md): Produce a deployable `output/` directory by running the same app in build mode and reading the `BuildReport` for failures. - [Deploy to GitHub Pages](https://usepennington.github.io/pennington/_llms/how-to/deployment/github-pages.md): Ship a Pennington site to GitHub Pages with a ready-to-copy Actions workflow, base-URL injection, and the `.nojekyll` marker. - [Adapt the deploy workflow for other hosts](https://usepennington.github.io/pennington/_llms/how-to/deployment/adapt-for-other-hosts.md): Port the canonical GitHub Pages recipe to Azure Static Web Apps, Cloudflare Pages, or Netlify by swapping four shared values and dropping in one host-specific config file. - [Self-host behind Nginx or IIS](https://usepennington.github.io/pennington/_llms/how-to/deployment/self-host.md): Serve the generated `output/` directory from Nginx or IIS with pretty-URL rewrites and the generated `404.html` as the fallback. - [Host under a sub-path (base URL)](https://usepennington.github.io/pennington/_llms/how-to/deployment/base-url.md): Serve a Pennington site from a non-root URL by passing `[baseUrl]` to the build and letting `BaseUrlHtmlRewriter` prefix every internal href, src, and action. ## Explanation ### Core - [The content pipeline and union types](https://usepennington.github.io/pennington/_llms/explanation/core/content-pipeline.md): Why Pennington models content as a four-case union that flows Discovered to Parsed to Rendered — with Failed as a peer case rather than an exception. - [ContentSource: constructing and pattern-matching the union](https://usepennington.github.io/pennington/_llms/explanation/core/content-source.md): How to construct a ContentSource case in DiscoverAsync, and how to consume one with C# 15 pattern matching — including the net10.0 compatibility shim. - [Dev mode and build mode share one code path](https://usepennington.github.io/pennington/_llms/explanation/core/dev-vs-build.md): Why the static build is an HTTP crawler against the running app, not a second renderer — keeping dev fidelity and publish output in lockstep. - [The front-matter capability system](https://usepennington.github.io/pennington/_llms/explanation/core/front-matter-capabilities.md): How IFrontMatter distinguishes universal capabilities (as default members) from selective ones (as separate interfaces) — and why presence of a capability interface is a meaningful signal. - [The response-processing pipeline](https://usepennington.github.io/pennington/_llms/explanation/core/response-processing.md): Why Pennington splits response rewriting into generic body processors and HTML-DOM rewriters that share one AngleSharp pass. - [When is DocSite the right starting point?](https://usepennington.github.io/pennington/_llms/explanation/core/docsite-positioning.md): Why AddDocSite is a template-style fast path with a fixed skeleton — and the narrow set of shapes that push you down to bare AddPennington instead. ### Rendering - [MonorailCSS integration](https://usepennington.github.io/pennington/_llms/explanation/rendering/monorail-css.md): Why Pennington scans classes off the wire during the response and generates the stylesheet on demand instead of pre-building a static CSS file. - [The syntax-highlighting cascade](https://usepennington.github.io/pennington/_llms/explanation/rendering/highlighting.md): Why Pennington dispatches code fences through a priority-ordered chain of highlighters with a guaranteed plain-text fallback instead of a single parser. ### Routing - [URL paths and content routes](https://usepennington.github.io/pennington/_llms/explanation/routing/url-paths.md): Why Pennington models URLs and filesystem paths as value-type records and why ContentRoute separates canonical identity from output location. - [Navigation-tree construction](https://usepennington.github.io/pennington/_llms/explanation/routing/navigation-tree.md): How Pennington folds a flat list of ContentTocItems into the sidebar tree using HierarchyParts, folder-derived sections, and min-of-children ordering. - [Cross-reference resolution](https://usepennington.github.io/pennington/_llms/explanation/routing/cross-references.md): Why Pennington links pages by symbolic uid rather than filesystem path, and how the two-phase resolver turns those uids into canonical URLs without the authoring cost of hand-coded links. ### Spa - [SPA navigation and island architecture](https://usepennington.github.io/pennington/_llms/explanation/spa/islands.md): Why Pennington ships a JSON envelope at a data path and selectively hydrates server-rendered islands instead of swapping full pages or going full SPA. ### Localization - [Locale-aware URLs and content fallback](https://usepennington.github.io/pennington/_llms/explanation/localization/urls-and-fallback.md): Why Pennington treats the URL path prefix as the authoritative locale signal, how ContentResolver strips it and falls back to the default locale, and why the search index is split per locale. ### Dev Experience - [Hot reload and file watching](https://usepennington.github.io/pennington/_llms/explanation/dev-experience/hot-reload.md): Why Pennington ships its own file watcher and WebSocket reload channel, and how the dev-only script is kept out of published builds.