---
title: Organize content with sections and areas
description: "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."
canonical_url: https://usepennington.net/tutorials/docsite/sections-and-areas/
sidecar_url: https://usepennington.net/tutorials/docsite/sections-and-areas.md
content_hash: sha256:6719837ac5b17dbd856ebb0d93d8a50c98469c1d14903815893dc39eb6657d38
tokens: 4783
uid: tutorials.docsite.sections-and-areas
reading_time_minutes: 7
---

Getting Started
# Organize content with sections and areas

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.

 
By the end of this tutorial the DocSite runs at `http://localhost:5000` with an area selector showing **Guides** and **Reference**. Each area renders its own grouped sidebar: *Getting Started* and *Advanced* under Guides, *Core API* and *Extensions* under Reference, with pages sorted by `order:` inside each group. For the algorithm behind the sidebar, see [Why the sidebar mirrors your folders](https://usepennington.net/explanation/routing/navigation-tree.md).

 
## Prerequisites

 
 - .NET 10 SDK installed
 - Completed [Scaffold a documentation site with DocSite](https://usepennington.net/tutorials/docsite/scaffold.md) (provides the area-free `Content/guides/` host this tutorial turns into areas)
 - Completed [Add doc pages and link between them](https://usepennington.net/tutorials/docsite/first-doc-page.md) (so `DocSiteFrontMatter` keys like `sectionLabel:` and `order:` are already familiar)
 
 
The finished code for this tutorial lives in [examples/DocSiteSectionsExample](https://github.com/usepennington/pennington/tree/main/examples/DocSiteSectionsExample).

 
---

 
## 1. Register two areas and start from a flat page

 
So far `Content/` is area-free — every page shares one sidebar tree. This tutorial splits that into two switchable tabs, **Guides** and **Reference**, then fills each with grouped sections. To watch the grouping build up from nothing, reset the Guides area to a single flat page first: delete the `configure.md` and hub `index.md` you added in [Add doc pages and link between them](https://usepennington.net/tutorials/docsite/first-doc-page.md), leaving just `install.md`. Then register the areas and strip `install.md` back to minimal front matter, so the sidebar starts as a single ungrouped entry before any sections appear.

 
**Register two content areas in `Program.cs`**

 
Add two `ContentArea` entries — `Guides` bound to `guides/` and `Reference` bound to `reference/`. Each binds a top-level folder under `Content/` to its own sidebar tab, and the selector appears once more than one area is configured. Every other change in this tutorial is a filesystem change under `Content/`.

 
```csharp:symbol
using Pennington.DocSite;
  
var builder = WebApplication.CreateBuilder(args);
  
// Same DocSite host shape as apps #4 and #5 — the focus here is on the
// *structure* of `Content/`. Two areas, each broken into two subfolder-backed
// sections. `NavigationBuilder` (inside `MainLayout`) turns the discovered
// flat TOC list into a grouped sidebar: each subfolder under an area becomes
// a non-navigable section header, and the pages inside sort by their front
// matter `order:` (tiebreaker: title).
builder.Services.AddDocSite(() => new DocSiteOptions
{
    SiteTitle = "Sections Docs",
    SiteDescription = "Structure Content/ into areas and sections using subfolders, section, and order front matter.",
    GitHubUrl = "https://github.com/usepennington/pennington",
    HeaderContent = """<a href="/">Sections Docs</a>""",
    FooterContent = """<footer class="mt-16 py-8 text-center text-sm text-base-500">Built with Pennington DocSite.</footer>""",
  
    // Two areas bound to two top-level content folders. The sidebar renders
    // an area selector above the per-area TOC; each area's TOC is grouped
    // by subfolder (sections "Getting Started" / "Advanced" under guides,
    // "Core Api" / "Extensions" under reference).
    Areas =
    [
        new ContentArea("Guides", "guides"),
        new ContentArea("Reference", "reference"),
    ],
});
  
var app = builder.Build();
  
app.UseDocSite();
  
await app.RunDocSiteAsync(args);
```

 
**Strip `Content/guides/install.md` back to minimal front matter**

 
Leave `Content/guides/install.md` with just a `title:` and a `description:` — drop the `order:`, `uid:`, and the Next footer from the earlier tutorial so the page starts as a bare entry.

 
```markdown:symbol
---
title: Install Pennington
description: Add the Pennington package to a new or existing ASP.NET project.
---
  
The first thing every new Pennington site needs is the package itself.
```

 
With no subfolder, the page is a single ungrouped entry directly under the Guides area — there is no section header because there is no folder to title-case into one. A missing `order:` defaults to `int.MaxValue`, so an un-ordered page sorts *after* any page with an explicit `order:` — but here it is the only page, so it just appears on its own.

 
> [!CHECKPOINT]
> The sidebar shows the page directly, with no section header above it.
> 
>  - Run `dotnet run` from your project and visit `http://localhost:5000/guides/install`
>  - The Guides sidebar shows the **Install Pennington** link directly under the area with no section header above it

---

 
## 2. Move the page into a subfolder to create a section

 
Now let's move the same page under a `getting-started/` subfolder and add `sectionLabel:` plus `order:` to the front matter. The sidebar gains its first grouped section header.

 
**Move `install.md` under `Content/guides/getting-started/`**

 
Delete `Content/guides/install.md` and create `Content/guides/getting-started/installation.md` in its place. The subfolder name is what creates the sidebar section header — Pennington title-cases the folder (`getting-started` → *Getting Started*) and renders it as a non-navigable group label.

 
Moving the file changes its URL. Routes preserve subfolders, so the page no longer serves at `/guides/install` — it now serves at `/guides/getting-started/installation/`, mirroring its path under `Content/`. The folder you added for grouping also became a URL segment.

 
**Add `sectionLabel: Getting Started` and `order: 10` to the front matter**

 
```markdown:symbol
---
title: Install Pennington
description: Add the Pennington package to a new or existing ASP.NET project.
sectionLabel: Getting Started 
order: 10 
---
  
The first thing every new Pennington site needs is the package itself.
```

 
`order:` sorts pages within the section (smaller first). `sectionLabel:` surfaces in breadcrumbs and prev/next chrome.

 
> [!CHECKPOINT]
>  - The old `http://localhost:5000/guides/install` URL now 404s — the page moved. Visit `http://localhost:5000/guides/getting-started/installation/` instead
>  - The Guides sidebar shows a non-navigable **Getting Started** header with the **Install Pennington** link indented under it
>  - The breadcrumb at the top of the article reads *Guides › Getting Started › Install Pennington*

---

 
## 3. Fill in the rest of the Guides area

 
Let's add the remaining pages to `getting-started/` and `advanced/` so Guides has two sibling sections with staggered `order:` values.

 
**Add two more pages to `getting-started/` with `order: 20` and `order: 30`**

 
Add the Guides landing page and two more pages to the `getting-started/` subfolder. Give `first-project.md` an `order:` of `20` and `configuration.md` an `order:` of `30`. Each page also carries `sectionLabel: Getting Started`.

 
```markdown:symbol
---
title: Guides
description: Walk-throughs for getting productive with Pennington, grouped by skill level.
sectionLabel: Guides
order: 0
---
  
Welcome to the **Guides** area. The sidebar groups every page in this area by
the subfolder it lives in — *Getting Started* collects the first three pages
you want to read, *Advanced* holds the deeper material. Within each group,
pages are ordered by the `order:` front-matter key.
  
Pick a page from the sidebar to jump in.
```

 
````markdown:symbol
---
title: Create your first project
description: Wire AddPennington and UsePennington into Program.cs and drop in a markdown page.
sectionLabel: Getting Started
order: 20
---
  
With the package installed, the smallest useful site is two lines of
registration and one markdown file on disk.
  
## Program.cs
  
```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPennington();
  
var app = builder.Build();
app.UsePennington();
await app.RunOrBuildAsync(args);
```
  
## Drop in a page
  
Create `Content/index.md` with a `title:` front-matter key and a body. Run
`dotnet run` and the page is served from `/` with hot reload on file change.
  
The next guide covers the handful of options you will reach for first.
````

 
```markdown:symbol
---
title: Configure your site
description: Tour the PenningtonOptions knobs you will reach for on day one.
sectionLabel: Getting Started
order: 30
---
  
`AddPennington` accepts a configuration callback that lets you tune the
content root, URL style, and a handful of feature toggles without leaving
`Program.cs`.
  
## Point at a content folder
  
By default Pennington reads from `Content/` next to your project. Override
the path when your content lives elsewhere in the repo or ships from an
embedded resource.
  
## Change the URL style
  
`LowercaseUrls` and `AppendTrailingSlash` control the shape of every
generated link. Pick a style early — changing it later invalidates existing
inbound links unless you pair the change with redirect front matter.
  
The next area — *Advanced* — covers layout overrides and the response
pipeline.
```

 
The 10/20/30 spacing leaves room to drop pages in later without renumbering. The minimum `order:` value in the section is `10` — that matters in the next step.

 
**Add the `advanced/` section with `order: 40` and `order: 50`**

 
Create `Content/guides/advanced/` and add two pages with `sectionLabel: Advanced` and `order:` values of `40` and `50`.

 
```markdown:symbol
---
title: Swap in a custom layout
description: Override the default DocSite layout with your own Razor component.
sectionLabel: Advanced
order: 40
---
  
DocSite ships with a sensible default layout, but every surface is a plain
Razor component. When you need a different header, footer, or body grid,
register your layout after `AddDocSite` and it takes precedence.
  
## Replace the main layout
  
Pass `AdditionalRoutingAssemblies` a reference to the assembly that holds
your `MyLayout.razor`, then mark it with the same `@layout`/`@inherits`
shape DocSite's `MainLayout` uses.
  
## Keep the sidebar or write your own
  
The easiest path is to keep `AreaNavigation` and `TableOfContentsNavigation`
inside your custom layout so sidebar grouping still works exactly as this
tutorial describes. The harder path — writing a bespoke nav — is covered
in a later how-to.
```

 
````markdown:symbol
---
title: Hook into the response pipeline
description: Intercept rendered HTML before it reaches the browser with IResponseProcessor.
sectionLabel: Advanced
order: 50
---
  
Every rendered page flows through a response pipeline before hitting the
wire. Register an `IResponseProcessor` to mutate the HTML — add a feedback
widget, rewrite anchor IDs, or inject analytics — without touching the
markdown.
  
## Register a processor
  
```csharp
builder.Services.AddSingleton<IResponseProcessor, MyProcessor>();
```
  
Processors run in `Order` ascending. Override `ShouldProcess` to scope the
work to particular routes, content types, or request metadata.
  
## Where this fits vs islands
  
Response processors mutate the already-rendered HTML server-side, before it
reaches the browser. Islands — the SPA engine's `data-spa-region` blocks —
mark which server-rendered regions swap in on in-site navigation; Pennington
renders them on the server with no client hydration. Reach for a processor to
change the HTML every page ships; for interactive client behavior, ship your
own client-side script that enhances the rendered HTML.
````

 
Section headers inherit the minimum `order:` of their pages. Leaving gaps between section order ranges — `getting-started/` at 10/20/30, `advanced/` at 40/50 — keeps *Getting Started* above *Advanced* without relying on alphabetical tie-breaks.

 
> [!CHECKPOINT]
>  - Revisit `http://localhost:5000/guides/getting-started/installation/`
>  - The Guides sidebar shows, top to bottom: **Getting Started** (with *Install Pennington*, *Create your first project*, *Configure your site*) then **Advanced** (with *Custom layouts*, *The response pipeline*)
>  - Click around — breadcrumbs and prev/next labels reflect the `sectionLabel:` on each page

---

 
## 4. Populate the Reference area to confirm it repeats the pattern

 
The same subfolder-plus-staggered-order pattern applies to the `Reference` area. Switching between both areas through the sidebar's area selector confirms each gets its own independent sidebar tree.

 
**Fill in `Content/reference/core-api/` with `order: 10` and `order: 20`**

 
Create the `core-api/` subfolder under `Content/reference/` and add two pages, each with `sectionLabel: Core API` and `order:` values of `10` and `20`. The folder creates the section, the key labels it, and the staggered numbers keep sibling sections predictable.

 
```markdown:symbol
---
title: Reference
description: API surface for the Pennington library, split into core and extension packages.
sectionLabel: Reference
order: 0
---
  
The **Reference** area mirrors the package structure: *Core API* covers the
types in `Pennington` itself, *Extensions* covers the optional surface
(Markdig extensions, content services). Page order inside each section
matches the order you are likely to discover the types while building a
site.
  
Pick a page from the sidebar to inspect a specific surface.
```

 
```markdown:symbol
---
title: PenningtonOptions
description: Core configuration surface handed to AddPennington.
sectionLabel: Core API
order: 10
---
  
`PenningtonOptions` is the record the configuration callback on
`AddPennington` mutates. It holds a handful of knobs that apply to every
page in the site regardless of content source.
  
## Keys worth knowing
  
- `ContentRootPath` — folder (or embedded-resource root) content is
  discovered from.
- `LowercaseUrls` — whether to force every generated URL to lowercase.
- `AppendTrailingSlash` — pick slash vs no-slash for clean URLs.
- `UrlStyle` — `Clean` (folder/index.html) or `Extension` (filename.html).
  
Every option has a sensible default; populate only the ones you need to
deviate from.
```

 
```markdown:symbol
---
title: ContentPipeline
description: The discovery/parse/render pipeline every content source flows through.
sectionLabel: Core API
order: 20
---
  
`ContentPipeline` is the assembly line `IContentService` implementations
feed into. Each source yields `DiscoveredItem`s, the pipeline parses them
into `ParsedItem`s via `IContentParser`, and finally renders them into
`RenderedItem`s via `IContentRenderer`.
  
## The three stages
  
1. **Discover** — each registered `IContentService` walks its source (disk,
   Razor pages, a JSON feed, whatever) and emits `DiscoveredItem` unions.
2. **Parse** — the matching `IContentParser` reads the item, separates
   front matter from body, and produces a `ParsedItem`.
3. **Render** — `IContentRenderer` turns the parsed body into HTML (plus
   an outline of headings and any diagnostics).
  
Custom parsers and renderers plug into the same pipeline — see the
*Extensions* section in the sidebar for how to write one.
```

 
**Add `Content/reference/extensions/` with `order: 30` and `order: 40`**

 
Create `extensions/` and drop two pages in it with `sectionLabel: Extensions` and `order:` values of `30` and `40`. Continuing the count rather than restarting at `10` keeps the section order ranges separated — *Core API* at 10/20, *Extensions* at 30/40 — so *Core API* sorts above *Extensions* by the same minimum-`order:` rule from unit 3.

 
```markdown:symbol
---
title: Markdown extensions
description: The Markdig extensions Pennington ships with — alerts, tabbed code, highlighting.
sectionLabel: Extensions
order: 30
---
  
Pennington configures Markdig with a curated set of extensions that light
up the authoring syntax the tutorials lean on.
  
## What ships in the box
  
- **Alerts** — GitHub-flavoured block quotes (`> [!NOTE]`, `TIP`,
  `IMPORTANT`, `WARNING`, `CAUTION`).
- **Tabbed code groups** — two or more adjacent fenced blocks with
  `tabs=true title="…"`.
- **Syntax highlighting** — TextMate grammars and ANSI shell output.
- **Code annotations** — trailing-comment `[!code highlight]` markers.
  
Registering your own extension is covered in the *Hook into the response
pipeline* guide's companion how-to.
```

 
```markdown:symbol
---
title: Custom content services
description: Teach Pennington a new content source by implementing IContentService.
sectionLabel: Extensions
order: 40
---
  
`IContentService` is the extension point for loading content from anything
that isn't plain markdown — a JSON feed, a database, a remote API, an
embedded resource. Register an implementation and the pipeline treats its
items exactly like every other source.
  
## The four methods
  
- `DiscoverAsync()` — yield a `DiscoveredItem` per logical page.
- `GetContentTocEntriesAsync()` — flat list of TOC entries with title,
  order, and hierarchy parts.
- `GetCrossReferencesAsync()` — any `uid`-to-route mappings you want the
  xref resolver to see.
- `GetContentToCopyAsync()` — assets the static-build step should copy
  alongside the rendered HTML.
  
Implementations live next to `MarkdownContentService` in the DI container
and are iterated in registration order.
```

 
**Switch areas with the sidebar's area selector**

 
Click the area selector pill at the top of the sidebar — the control that toggles between *Guides* and *Reference*. Each area has its own independent sidebar tree. The `ContentArea` bindings from `Program.cs` plus the subfolder layout are what make this work, with no extra code.

 
> [!CHECKPOINT]
>  - With the host running, visit `http://localhost:5000/reference/core-api/pennington-options`
>  - The sidebar shows **Core API** above **Extensions**, with two pages under each in `order:` sequence
>  - Click the area selector to **Guides** — the sidebar replaces itself with the *Getting Started* / *Advanced* groups from unit 3
>  - The area selector tracks the current area as navigation moves between pages

---

 
## Summary

 
 - A DocSite's `Content/` folder splits into multiple `ContentArea` entries, and each one gets its own sidebar tree.
 - **The subfolder name creates the sidebar section** — `sectionLabel:` is metadata for breadcrumbs and prev/next labels, not a grouper.
 - Staggered `order:` values across sibling sections (10/20/30 for one, 40/50 for the next) sort section headers in the intended order, without relying on alphabetical tie-breaks between folder names.
 - The shape of the generated sidebar is predictable from the shape of the `Content/` folder before running the site.
 
 
[Previous
                
                Add doc pages and link between them](https://usepennington.net/tutorials/docsite/first-doc-page.md)[Next
                    
                Add a Razor landing page at the site root](https://usepennington.net/tutorials/docsite/landing-page.md)