Blog Controller — The /posts/ Listing Page

Configure the /posts/ blog listing page in Dax Pro: pagination, ordering, and post card layout. The blog controller generates the listing automatically from content/posts/*.md. Edit directly in content/posts.md.

What the Blog Controller Is

The blog controller is the file that generates the /posts/ listing page — the archive that shows all your blog articles, paginated. It is a single file in content/:

content/posts.md

When Dax Pro builds the site, it reads this file and generates:

  • /posts/ — the archive page (page 1)
  • /posts/page/2/ — page 2, if there are more than pagination items
  • /posts/page/3/ — page 3, and so on
  • Individual blog articles live in content/posts/*.md and each has its own URL (/posts/{slug}/). The controller is what creates the listing page that visitors browse to find those articles.

    Without the controller, individual post pages would still be built, but there would be no /posts/ index. Visitors arriving at your domain and clicking "Insights" or "Blog" in the navigation would hit a 404.

    The controller is the entry point to your blog. It is required.

    This chapter covers:

    1. The controller file structure — what each field means

    2. How the listing renders — what visitors see

    3. Changing pagination — how many posts per page

    4. Changing ordering — how posts are sorted

    5. Changing the page title and description — the SEO of the listing page

    6. The difference between the blog listing and the homepage Insights section — two places posts appear, two different purposes

    7. Editing the controller — since Decap CMS does not expose it, only direct edit is available


    The Controller File Structure

    The default content/posts.md looks like this:

    ---
    

    layout: "posts-list.dax"

    title: "Engineering Publications & Blueprints"

    breadcrumb: "Technical Insights"

    description: "Architectural analyses, performance benchmarks, and headless content modeling strategies for the modern .NET enterprise ecosystem."

    collection: "posts"

    pagination: 6


    Every field explained:

    Field Purpose
    layout The template used to render the listing. Must be posts-list.dax. Do not change.
    title The page's H1 and browser title.
    breadcrumb Short label for the breadcrumb bar.
    description Meta description for SEO.
    collection The folder whose items are listed. Must be posts. Do not change.
    pagination Number of posts per page.

    Only four fields are user-editable: title, breadcrumb, description, pagination. Everything else is fixed.

    Do not add a body to this file. The controller's body is not rendered. Everything is rendered by the template.

    Do not change collection: "posts". The value must match the folder name.

    Do not change layout: "posts-list.dax". It must be the blog listing template.


    How the Listing Renders

    When a visitor opens https://yoursite.com/posts/, Dax Pro renders the posts-list.dax layout with:

    1. Page header — breadcrumb and title from the frontmatter

    2. Post grid — one card per post, styled as a blog card with image, category, date, read time, title, excerpt, author, and read-more link

    3. Pagination — Previous / current / Next, shown only if there are multiple pages

    Each post card contains:

  • Featured image — the post's image field, or the site's default image from metadata.yaml if the post has no image
  • Meta line — category, date, read time
  • Title — the post's title, linked to /posts/{slug}/
  • Excerpt — the post's excerpt (or description as fallback)
  • Footer — author name and read-more link
  • The cards are clickable. Clicking anywhere on the card goes to /posts/{slug}/.

    The grid layout is three columns on desktop, two on tablet, one on mobile. Defined in style.css.

    Ordering is by date in descending order. The most recent post appears first.


    The Blog Listing vs. the Homepage Insights

    Dax Pro shows blog posts in two places:

    1. /posts/ — the full blog archive. Shows all posts, paginated. This is the destination for "read all" and "view all" links.

    2. / (homepage) — the Insights section. Shows the three most recent posts only. This is a teaser section on the marketing homepage.

    These are separate. Editing the blog controller (content/posts.md) changes /posts/. Editing the homepage Insights section (content/index.md → insights) changes the three-post teaser on the homepage.

    You cannot control which posts appear on the homepage from the controller. The homepage always shows the three most recent posts by date. To change which posts appear there, change the date field of individual posts.

    The blog controller does not control the number of posts on the homepage. That is fixed at three by the home/insights.dax partial.

    Why does this distinction matter? Because the two places serve different purposes:

  • Homepage Insights — a promise of ongoing content. "We write. Here is recent proof."
  • /posts/ — the full archive for readers who want more.
  • If you have only three posts, both sections show the same three posts. If you have twenty, the homepage shows three and /posts/ shows all twenty across four pages.


    Changing Pagination

    pagination: 6 means six posts per page. With 20 posts and pagination: 6:

  • /posts/ shows posts 1–6
  • /posts/page/2/ shows posts 7–12
  • /posts/page/3/ shows posts 13–18
  • /posts/page/4/ shows posts 19–20
  • With pagination: 9 and 20 posts:

  • /posts/ shows posts 1–9
  • /posts/page/2/ shows posts 10–18
  • /posts/page/3/ shows posts 19–20
  • Choosing a value:

    Number of posts Recommended pagination
    1–6 6 (no pagination appears)
    7–12 9 (paginated, 2 pages)
    13–24 9 (paginated, 3 pages)
    25–48 12 (paginated, 3–4 pages)
    49+ 12 or 15 (paginated, 4+ pages)

    Rule of thumb: pick a value that fills at least one full page and creates at least two pages when you have more than one page's worth. For small blogs, pagination can look odd — if you have four posts, set pagination: 12 and no pagination appears.

    To change pagination:

    1. Open content/posts.md in VS Code

    2. Change pagination: 6 to your preferred value

    3. Save

    4. Rebuild

    dotnet run -- build
    

    For deployed sites:

    git add content/posts.md
    

    git commit -m "Change blog pagination to 9"

    git push

    Why is the controller not in Decap CMS? The "Posts" collection in Decap CMS exposes individual post files (content/posts/*.md), not the controller file. The controller is configuration, not content, and is left out of the CMS to prevent accidental edits (changing collection: "posts" to another value would break the listing).


    Changing Ordering

    The listing orders posts by date in descending order. The date field is on each individual post file (content/posts/*.md), not on the controller.

    To reorder posts:

    1. Open each content/posts/*.md file

    2. Change the date field to control the order

    3. Rebuild

    Example: you have three posts and want them in this order:

    1. "Latest Announcement" (should be first)

    2. "Technical Deep Dive" (should be second)

    3. "Old Tutorial" (should be third)

    Set:

    content/posts/latest-announcement.md:

    date: 2026-05-01
    

    content/posts/technical-deep-dive.md:

    date: 2026-04-01
    

    content/posts/old-tutorial.md:

    date: 2026-03-01
    

    The dates control order. The most recent appears first.

    Editing dates in bulk: if you want to change many posts' dates or ordering, edit the files directly and push once. Editing through the CMS triggers one rebuild per post.

    Draft posts and future dates: if you set a post's date to a future date (e.g., 2027-01-01), the post is still published — Dax Pro does not know about "drafts" or "scheduling." It builds whatever is in the folder. To unpublish a post, delete its file. To schedule a post, deploy the file on the scheduled date. There is no built-in scheduling.

    If you want draft support: create a _drafts/ folder outside content/ and move posts there until you are ready. Dax Pro only builds files in content/. This is a manual workflow, not automatic.


    Changing the Page Title and Description

    The listing page's title, breadcrumb, and description are on the controller.

    To change via direct edit:

    1. Open content/posts.md

    2. Update title, breadcrumb, description

    3. Rebuild

    ---
    

    layout: "posts-list.dax"

    title: "Our Blog"

    breadcrumb: "Blog"

    description: "Articles about .NET, static sites, performance, and enterprise web development."

    collection: "posts"

    pagination: 9


    These values appear in:

  • Title — the

    of /posts/, the browser tab, the OpenGraph title

  • Breadcrumb — the crumb "Home / Blog" (or whatever you set)
  • Description — the and OpenGraph description
  • Optimizing for SEO:

  • Title: under 60 characters. Include your topic keyword. Example: "Dax Pro Blog — .NET, Static Sites, and Performance"
  • Description: under 160 characters. Include your primary topics. Example: "Articles on .NET static site generation, Decap CMS, and web performance. Written by the Dax Pro team."
  • Breadcrumb: one to three words. "Blog" or "Articles" or "Insights".

  • Editing the Blog Controller

    There is no way to edit the blog controller via Decap CMS in the current Dax Pro configuration. The "Posts" collection in Decap CMS is content/posts/ (the individual files). The controller content/posts.md is not exposed.

    To change the controller, edit directly:

    1. Open content/posts.md in VS Code

    2. Edit title, breadcrumb, description, or pagination

    3. Save

    4. Rebuild

    dotnet run -- build
    

    If you want the controller in the CMS, add a file collection to config.yml:

      - name: "posts_controller"
    

    label: "📝 Blog Listing"

    files:

    - name: "posts_listing"

    label: "Blog Listing"

    file: "content/posts.md"

    fields:

    - { label: "Layout", name: "layout", widget: "hidden", default: "posts-list.dax" }

    - { label: "Title", name: "title", widget: "string" }

    - { label: "Breadcrumb", name: "breadcrumb", widget: "string" }

    - { label: "Description", name: "description", widget: "text" }

    - { label: "Collection", name: "collection", widget: "hidden", default: "posts" }

    - { label: "Pagination", name: "pagination", widget: "number", default: 6 }

    This is a developer task — see the Developer Guide. For most sites, the controller changes rarely, so direct editing is fine.


    Adding and Removing Posts

    The controller does not need to be updated when posts are added or removed. It is a dynamic listing — it always shows whatever is in content/posts/.

    To add a post:

    1. Create content/posts/new-post.md

    2. Fill in the frontmatter (title, description, date, etc.)

    3. Write the body

    4. Rebuild

    The post appears in /posts/ automatically at its date-sorted position.

    To remove a post:

    1. Delete content/posts/old-post.md

    2. Rebuild

    The post disappears from /posts/ automatically.

    To unpublish without deleting: there is no built-in unpublish. Either delete the file or move it outside content/. If you want to keep the content for later, move it to a _archive/ folder outside content/.


    The Full Frontmatter Reference

    ---
    

    layout: "posts-list.dax"

    title: "Engineering Publications & Blueprints"

    breadcrumb: "Technical Insights"

    description: "Architectural analyses, performance benchmarks, and headless content modeling strategies for the modern .NET enterprise ecosystem."

    collection: "posts"

    pagination: 6


    Field-by-field:

    Field Type Required Change?
    layout string Yes No — always posts-list.dax
    title string Yes Yes — your page title
    breadcrumb string No Yes — short nav label
    description string Yes Yes — meta description
    collection string Yes No — always posts
    pagination number Yes Yes — posts per page

    Total configurable: 4 fields. Everything else is fixed.


    Common Blog Controller Mistakes

    Mistake 1: Changing collection: "posts".

    The value must match the folder name (posts). Changing it to services or any other value breaks the listing.

    Mistake 2: Adding a body to the controller.

    Anything after the closing --- is ignored. The controller renders through the template.

    Mistake 3: Setting pagination: 0.

    Zero means zero posts per page. The listing is empty.

    Mistake 4: Editing the wrong file for a post change.

    If you want to change a post's title, edit content/posts/{slug}.md. If you want to change the pagination, edit content/posts.md. They are different files.

    Mistake 5: Deleting the controller.

    Without content/posts.md, the /posts/ listing is not generated. Every individual post URL still works, but the archive page is 404.

    Mistake 6: Editing the homepage Insights for blog changes.

    To change a post's teaser on the homepage, edit the post's date (to control which three appear) or the post's title/excerpt (to change what displays). Editing insights.title on index.md changes the section heading, not the posts.

    Mistake 7: Forgetting to rebuild.

    Any change requires a build. If you edit and refresh with no visible change, you forgot dotnet run -- build.

    Mistake 8: Using uppercase tags on posts.

    Uppercase tags break tag pages. Always lowercase.


    Verifying the Listing

    After editing, verify:

    1. URL works — navigate to /posts/. The page loads.

    2. Title — browser tab and page H1 match the controller's title.

    3. Breadcrumb — shows "Home / {breadcrumb}".

    4. Description — view source, confirm matches.

    5. Grid — post cards appear in a 3-column grid (desktop).

    6. Card content — each card has an image, category/date/read-time meta line, title, excerpt, author, and read-more link.

    7. Ordering — most recent post appears first.

    8. Pagination — if you have more posts than pagination, pagination links appear at the bottom.

    9. Click through — click a card, confirm it goes to /posts/{slug}/.

    10. Mobile — resize to mobile. Cards stack vertically, images scale, text wraps.

    If a post does not appear:

  • The file exists in content/posts/
  • The frontmatter is valid
  • The file has layout: "post.dax"
  • The build completed without errors
  • If ordering is wrong:

  • Check the date field in each post
  • Most recent date appears first
  • Ties are broken alphabetically by URL
  • If pagination is missing:

  • The number of posts is less than or equal to pagination
  • The template only shows pagination when there are multiple pages

  • What You Have Now

    After this chapter, you can:

  • ✅ Understand what the blog controller does
  • ✅ Change the /posts/ listing title, breadcrumb, and description
  • ✅ Change pagination (posts per page)
  • ✅ Understand ordering (by date, descending)
  • ✅ Distinguish the blog listing (/posts/) from the homepage Insights
  • ✅ Edit the controller directly in content/posts.md
  • ✅ Add and remove posts without touching the controller
  • ✅ Verify the listing, pagination, and ordering
  • ✅ Avoid the eight most common blog controller mistakes
The blog controller is done. The next chapter covers Blog Post Collections — the individual post files in content/posts/*.md that produce /posts/{slug}/ pages, including their frontmatter, tags, and images.

→ Continue to Blog Post Collections