Publish a blog

Any Markdown page with a date in its front matter becomes a blog post. Pages without a date stay ordinary documentation pages.

---
title: Announcing 1.0
date: 2026-09-14
summary: What changed on the road to 1.0.
tags: [release, fsharp]
category: news
slug: announcing-1-0
series: Road to 1.0
seriesOrder: 3
comments: true
---

Post front matter

Field Default Meaning
date none Publication date (yyyy-mm-dd). Makes the page a post.
tags [] Topics. A post can have any number of tags.
category none One optional section of the blog, such as news.
draft false Leaves the post out of the build unless you pass --drafts.
summary first paragraph Short description shown in listings and the feed.
slug file name The post's URL segment.
series none Name of an ordered series of posts.
seriesOrder publication date Position within the series.
comments false Shows the site's comments embed on this post.
blogList none Default options for a {{< posts >}} listing on this page.

Tags, categories, and series are independent. Being in a series never implies a tag or category.

URLs

Each post is published at blog/<slug>/index.html. The date is never part of the URL, so correcting a date doesn't break links. Pick a slug when a post is first published and keep it.

Generated pages

A build generates these pages from every non-draft post. They use the same layout, theme, and navigation as the rest of the site.

Path Content
blog/index.html The ten newest posts, with a link to older posts.
blog/page/<n>/index.html The next ten posts, with newer and older links.
blog/tags/<tag>.html Every post with that tag, newest first.
blog/category/<category>.html Every post in that category, newest first.
blog/series/<series>.html A series' posts in order, labeled "Part n of m".
blog/feed.xml An Atom feed of every post.

Tag, category, and series names are lower-cased in these paths. Numbered pages such as blog/page/2/ shift as you publish, so link to posts, not to listing pages.

All links in generated pages and the feed are relative, so the site works when hosted under a sub-path.

Each post shows its date and an estimated reading time, based on the prose in its Markdown at 200 words per minute. Code blocks and shortcodes don't count.

A post in a series links to the previous and next parts of that series. Other posts link to the next newer and older posts.

Drafts

Posts with draft: true are left out of the whole build: their own pages, listings, series, and the feed. Preview them locally with:

livedocs watch --drafts

livedocs build --drafts works the same way. Never pass --drafts in a publishing build.

List posts on a page

Use the posts shortcode to list posts on any page. Write it on its own line, with a blank line before and after:

{{< posts tag="fsharp" limit="5" layout="preview" >}}
Attribute Default Meaning
tag none Only posts with this tag.
category none Only posts in this category. tag and category combine.
limit none The maximum number of posts.
layout list list (titles), compact (cards with dates), or preview (cards with summaries).
show depends on layout Comma-separated fields to show on a card: date, readingtime, summary, tags.

Any attribute you leave off can come from the page's blogList front matter. This keeps a page's listing settings with its other metadata:

---
title: News
blogList:
  layout: preview
  limit: 6
  category: news
  show: [date, summary]
---

{{< posts >}}

An attribute written in the shortcode overrides blogList. An unknown layout fails the build.

Show series navigation

Use {{< series-nav >}} on a post to list every part of its series. On a page without series front matter it renders nothing, and the build prints a warning. With --warn-as-error that warning fails the build.

Comments

FsLiveDocs doesn't store comments. It embeds a third-party provider on posts that set comments: true. Configure the provider once in .livedocs/config.json.

Giscus

Giscus keeps comments in GitHub Discussions. Copy the IDs from the giscus configuration page:

{
  "commentsProvider": {
    "kind": "giscus",
    "repo": "example/library",
    "repoId": "R_kgDO...",
    "category": "Announcements",
    "categoryId": "DIC_kwDO...",
    "theme": "preferred_color_scheme"
  }
}

theme is optional. It defaults to the site's theme.

Other providers

Use custom to paste any provider's embed snippet:

{
  "commentsProvider": {
    "kind": "custom",
    "html": "<script src=\"https://example.com/embed.js\" async></script>"
  }
}

FsLiveDocs inserts this HTML unchanged and doesn't test these providers.

Either way, a post shows a "Comments" heading that links to the embed. With Giscus the heading shows the number of comments once the embed has loaded in the browser. The build can't know the count, so the heading shows a placeholder until then.

Comment settings are never stored in release capsules. When you rebuild historical releases, they use the current configuration.