This site provides a machine-readable index at /llms.txt.

Skip to main content Skip to navigation

Use multiple content sources

When one markdown tree outgrows a single root — a /docs/ section alongside a separate /blog/ section, or a catch-all root paired with a specialised subtree — registering multiple content sources is the answer. The right recipe depends on the host: AddDocSite supports multiple folder-scoped sub-trees through ContentArea entries, while bare AddPennington allows any number of chained AddMarkdownContent<T> calls with independent front-matter types. For a first site, start with Add your first markdown page.

Assumptions

For a working DocSite multi-area setup, see examples/DocSiteKitchenSinkExample. For the bare AddPennington chained-sources recipe, see examples/MultipleSourcesExample; the helpers on ServiceConfiguration back each step below.


Steps

  1. 1

    Decide: DocSite areas, or chained AddMarkdownContent calls?

    AddDocSite owns exactly one markdown pipeline keyed on DocSiteFrontMatter; use ContentArea[] on DocSiteOptions.Areas to split it into folder-scoped sub-trees (continue to step 2). For two different front-matter types, or for a site already on bare AddPennington, chain AddMarkdownContent<T> calls instead (jump to step 4).

  2. 2

    (DocSite) Declare the areas

    Each ContentArea slug becomes both the URL prefix and the top-level folder under ContentRootPath.

    [
        new ContentArea("Main", "main"),
        new ContentArea("API", "api"),
    ]
  3. 3

    (DocSite) Wire the areas onto DocSiteOptions.Areas

    Assign the areas array when building DocSiteOptions — the relevant property is Areas = BuildAreas().

    new()
        {
            SiteTitle = "Kitchen Sink Docs",
            Description = "A wide-surface DocSite example that backs eighteen how-to pages.",
            GitHubUrl = "https://github.com/usepennington/pennington",
            CanonicalBaseUrl = "https://example.com/",
            HeaderContent = """<a href="/" class="font-bold">Kitchen Sink Docs</a>""",
            FooterContent = BuildFooter(),
            ColorScheme = BuildColorScheme(),
            DisplayFontFamily = "'DocSiteKitchenSinkDisplay', system-ui, sans-serif",
            BodyFontFamily = "'DocSiteKitchenSinkBody', system-ui, sans-serif",
            FontPreloads = BuildFontPreloads(),
            ExtraStyles = BuildExtraStyles(),
            ConfigureLocalization = ConfigureLocalization,
            Areas = BuildAreas(),
        }

    Skip to Verify.

  4. 4

    (Bare Pennington) Register the first markdown source

    Call AddMarkdownContent<TFrontMatter> inside AddPennington with a ContentPath that roots the first tree, a distinct BasePageUrl, and an optional SectionLabel to group the source's pages in navigation.

    md.ContentPath = "Content/docs";
    md.BasePageUrl = "/docs";
    md.SectionLabel = "Documentation";
  5. 5

    (Bare Pennington) Register the second markdown source

    Point the second AddMarkdownContent<T> at a different ContentPath and BasePageUrl; the front-matter type can differ from the first source.

    md.ContentPath = "Content/blog";
    md.BasePageUrl = "/blog";
    md.SectionLabel = "Blog";
  6. 6

    (Optional) Carve out an overlapping subtree with ExcludePaths

    When one source's ContentPath is a parent of another's, Pennington emits an overlap warning at startup because both pipelines would discover the inner tree and produce conflicting outputs. Adding ExcludePaths on the broader source gives the specialised source exclusive ownership of that subtree.

    md.ContentPath = "Content";
    md.BasePageUrl = "/";
    md.ExcludePaths = ["blog"];

Verify

  • Run dotnet run and visit each source's BasePageUrl. Confirm pages render under both prefixes
  • Startup logs contain no Markdown content source rooted at '…' overlaps… warnings (or, if expected, the warning text names the subtree intended for exclusion)
  • Each source's pages appear under the correct SectionLabel / ContentArea.Title in the generated navigation