April 7, 2026
Structured data and accessibility, on by default
By Phil Scott
Three things a content site should do for its readers are now handled by the DocSite and BlogSite templates: structured data for search engines, accessibility landmarks for keyboard and screen-reader users, and font preloading to reduce the flash of unstyled text.
Structured data, so search engines understand your pages
Search engines read HTML, but they reward pages that tell them what they're
looking at. Pennington emits schema.org JSON-LD as a <script> tag in every
page's head. The DocSite template wires Article, BreadcrumbList, and
WebSite automatically; the BlogSite template wires Article and WebSite:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Introducing Pennington",
"datePublished": "2026-04-04",
"author": { "@type": "Person", "name": "Phil Scott" }
}
</script>
That's what powers rich results — the breadcrumb trails and article cards in
search listings. It needs one thing: an absolute base URL. Set CanonicalBaseUrl
and the JSON-LD appears; leave it unset and Pennington skips it rather than emit
broken relative links. See hosting under a base
URL.
The framework ships only one type — JsonLdEntity. Templates own the concrete
records they emit, and you can add your own for any schema.org type the
templates don't cover. Subclass JsonLdEntity, attribute your fields with
[JsonPropertyName], and pass the entity to <StructuredData Entities="...">.
Pennington serializes it through the same path as the built-in types. See
Add a custom schema.org JSON-LD type
for a worked JsonLdRecipe example.
Accessibility landmarks
Pennington pages ship visually-hidden skip links so keyboard users can jump past
navigation straight to the content. The sidebar is a real <nav> element with
an aria-label, the header and footer navs are labeled too, and the main
content carries id="main-content" as the skip-link target.
None of this is configurable — these landmarks are wired up by default, so the skip link and labels are there from the first page render rather than something you have to remember to add.
Font preloading
Custom fonts load after the browser parses your stylesheet, which means a beat
of fallback text before the real typeface swaps in. A preload hint tells the
browser to fetch the font earlier. Pass a FontPreload[] to
DocSiteOptions.FontPreloads and Pennington emits the <link rel="preload">
tags ahead of the stylesheet:
new DocSiteOptions
{
FontPreloads =
[
new FontPreload("/fonts/body.woff2"),
new FontPreload("/fonts/display.woff2"),
],
};
The font how-to covers the full setup, including
the @font-face rules.