Skip to content

Commit cfe7506

Browse files
committed
feat(docs): leave drafts out of the production build
- docs: document drafts in frontmatter for Exclude, Auto Sidebar, Auto Navbar and the blog, with a live demo page - config: leave pages marked draft (or blog.draft) out of production builds, sidebars and navbars - deps: vitepress-exclude 0.2.0, auto-sidebar 0.10.0, auto-navbar 0.8.0
1 parent 499f914 commit cfe7506

8 files changed

Lines changed: 126 additions & 61 deletions

File tree

‎docs/.vitepress/config.mts‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ const menuTitleOptions = {
4444
// @binarynoir/vitepress-downloads: the same options go to all three pieces below.
4545
const downloadsOptions = { folders: ['downloads', 'files'] };
4646

47+
// @binarynoir/vitepress-exclude, -auto-sidebar and -auto-navbar: a page with `draft: true` in its
48+
// frontmatter (or a blog post with `blog: { draft: true }`) is left out of the production build,
49+
// its sidebar and its navbar. `docs:dev` still builds drafts so they can be previewed.
50+
const isBuild = process.env.NODE_ENV === 'production';
51+
const excludeFrontmatter = isBuild ? ['draft', 'blog.draft'] : [];
52+
4753
// @binarynoir/vitepress-blog: the same options go to all five pieces below.
4854
const blogOptions = {
4955
siteUrl: SITE_URL,
@@ -52,8 +58,8 @@ const blogOptions = {
5258
// Post times without an offset (`date: 2026-10-20 14:30`) are New York time, and every date
5359
// and time is shown in it, with its zone: "October 20, 2026 at 2:30 PM EDT".
5460
timeZone: 'America/New_York',
55-
// Posts marked `draft: true` are listed (with a Draft badge) in `docs:dev` only.
56-
drafts: process.env.NODE_ENV !== 'production',
61+
// Drafts are listed (with a Draft badge) in `docs:dev`; production builds leave them out.
62+
drafts: !isBuild,
5763
};
5864

5965
export default defineConfig({
@@ -66,9 +72,9 @@ export default defineConfig({
6672
srcExclude: [
6773
// Files in a `downloads` or `files` folder are published as-is, never built as pages.
6874
...downloadsSrcExclude(downloadsOptions),
69-
// @binarynoir/vitepress-exclude: whatever a folder's `.exclude` file lists is not built.
70-
// The same files hide those pages from the sidebar and navbar.
71-
...excludeSrc(docsRoot),
75+
// @binarynoir/vitepress-exclude: whatever a folder's `.exclude` file lists is not built, nor
76+
// a draft (see excludeFrontmatter above). The same rules hide those pages from the sidebar and navbar.
77+
...excludeSrc(docsRoot, { excludeFrontmatter }),
7278
],
7379

7480
// @binarynoir/vitepress-readme-index: a folder's README.md is served as its index page,
@@ -112,6 +118,7 @@ export default defineConfig({
112118
nav: generateNav(docsRoot, {
113119
maxDepth: 2,
114120
configFilenames: ['.nav', '.sidebar'],
121+
excludeFrontmatter,
115122
...menuTitleOptions,
116123
}),
117124

@@ -121,6 +128,7 @@ export default defineConfig({
121128
sidebar: generateSidebar(docsRoot, {
122129
maxDepth: 3,
123130
maxTitleLength: 50,
131+
excludeFrontmatter,
124132
...menuTitleOptions,
125133
groupTitleFromIndex: true,
126134
// Groups start closed, so @binarynoir/vitepress-sidebar-accordion (in the theme) has a caret to toggle.
@@ -212,7 +220,7 @@ export default defineConfig({
212220
// Publishes the files in each `downloads` or `files` folder at their page-relative URL.
213221
downloadsVitePlugin(downloadsOptions),
214222
// Restarts `vitepress dev` when an `.exclude` file changes, so new rules apply right away.
215-
excludeVitePlugin(),
223+
excludeVitePlugin({ excludeFrontmatter }),
216224
// Keeps blog lists current while running `vitepress dev`, and serves the feeds there.
217225
blogVitePlugin(blogOptions),
218226
],

‎docs/plugins/auto-navbar.md‎

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,24 @@ Each top-level folder becomes a navbar entry: a dropdown if it has several pages
5050

5151
## Options
5252

53-
| Option | Default | Description |
54-
| ------------------- | -------------- | ------------------------------------------------------------------------------------------ |
55-
| `maxDepth` | `2` | Navbar levels, 1 to 3. |
56-
| `maxTitleLength` | `50` | Truncate generated titles beyond this length. |
57-
| `configFilenames` | `['.nav']` | Per-folder config file names, checked in order. |
58-
| `excludeFilenames` | `['.exclude']` | Per-folder exclusion file names. |
59-
| `indexTitle` | `'Overview'` | Label for the dropdown entry that links to the folder's landing page. |
60-
| `flattenSinglePage` | `true` | A folder with one entry becomes a plain link. |
61-
| `activeMatch` | `true` | Keep items highlighted on every page beneath them. |
62-
| `titleReplacements` | `{}` | Words to replace in titles made from file and folder names. |
63-
| `titleCase` | `true` | Title-case titles made from file and folder names. |
64-
| `stripTags` | `true` | Remove Markdown Tags syntax (`((tag\|draft))`) from titles. |
65-
| `transformTitle` | none | A function that gets every title before truncation. |
66-
| `sort` | `'name'` | Order for unlisted entries in every folder, e.g. `'date desc'`. See [Settings](#settings). |
67-
| `limit` | `0` | Most unlisted entries every dropdown shows, after sorting. |
68-
| `more` | none | Link text for the landing page when a limit left entries out. |
69-
| `verbose` | `false` | Log each folder as it is processed. |
53+
| Option | Default | Description |
54+
| -------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
55+
| `maxDepth` | `2` | Navbar levels, 1 to 3. |
56+
| `maxTitleLength` | `50` | Truncate generated titles beyond this length. |
57+
| `configFilenames` | `['.nav']` | Per-folder config file names, checked in order. |
58+
| `excludeFilenames` | `['.exclude']` | Per-folder exclusion file names. |
59+
| `excludeFrontmatter` | `[]` | Frontmatter keys that hide a page when `true`, such as `['draft', 'blog.draft']`. See [Exclude](/plugins/exclude/#drafts-in-frontmatter). |
60+
| `indexTitle` | `'Overview'` | Label for the dropdown entry that links to the folder's landing page. |
61+
| `flattenSinglePage` | `true` | A folder with one entry becomes a plain link. |
62+
| `activeMatch` | `true` | Keep items highlighted on every page beneath them. |
63+
| `titleReplacements` | `{}` | Words to replace in titles made from file and folder names. |
64+
| `titleCase` | `true` | Title-case titles made from file and folder names. |
65+
| `stripTags` | `true` | Remove Markdown Tags syntax (`((tag\|draft))`) from titles. |
66+
| `transformTitle` | none | A function that gets every title before truncation. |
67+
| `sort` | `'name'` | Order for unlisted entries in every folder, e.g. `'date desc'`. See [Settings](#settings). |
68+
| `limit` | `0` | Most unlisted entries every dropdown shows, after sorting. |
69+
| `more` | none | Link text for the landing page when a limit left entries out. |
70+
| `verbose` | `false` | Log each folder as it is processed. |
7071

7172
Titles can be single- or double-quoted, with a space after the colon if you like, and a line that can't be parsed logs a warning with its file and line number.
7273

‎docs/plugins/auto-sidebar/index.md‎

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -188,23 +188,24 @@ index.md:All decisions
188188

189189
## Options
190190

191-
| Option | Default | Description |
192-
| --------------------- | -------------- | ---------------------------------------------------------------------------------------- |
193-
| `maxDepth` | `3` | How many folder levels deep to recurse. |
194-
| `maxTitleLength` | `50` | Truncate generated titles beyond this length. |
195-
| `configFilenames` | `['.sidebar']` | Per-folder ordering file names. |
196-
| `excludeFilenames` | `['.exclude']` | Per-folder exclusion file names. |
197-
| `collapsed` | `false` | Initial collapsed state for section headers. |
198-
| `flattenSinglePage` | `true` | A folder with one page becomes a plain link instead of its own group. |
199-
| `titleReplacements` | `{}` | Words to replace in titles made from file and folder names. |
200-
| `titleCase` | `true` | Title-case titles made from file and folder names. |
201-
| `stripTags` | `true` | Remove Markdown Tags syntax (`((tag\|draft))`) from titles. |
202-
| `transformTitle` | none | A function that gets every title before truncation. |
203-
| `sort` | `'name'` | Order for unlisted entries in every folder, e.g. `'date desc'`. See [Sorting](#sorting). |
204-
| `limit` | `0` | Most unlisted entries every folder shows, after sorting. See [Limiting](#limiting). |
205-
| `more` | none | Link text for the landing page when a limit left entries out. |
206-
| `groupTitleFromIndex` | `false` | Label a folder's group with its landing page's title. |
207-
| `verbose` | `false` | Log each folder as it is processed. |
191+
| Option | Default | Description |
192+
| --------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
193+
| `maxDepth` | `3` | How many folder levels deep to recurse. |
194+
| `maxTitleLength` | `50` | Truncate generated titles beyond this length. |
195+
| `configFilenames` | `['.sidebar']` | Per-folder ordering file names. |
196+
| `excludeFilenames` | `['.exclude']` | Per-folder exclusion file names. |
197+
| `excludeFrontmatter` | `[]` | Frontmatter keys that hide a page when `true`, such as `['draft', 'blog.draft']`. See [Exclude](/plugins/exclude/#drafts-in-frontmatter). |
198+
| `collapsed` | `false` | Initial collapsed state for section headers. |
199+
| `flattenSinglePage` | `true` | A folder with one page becomes a plain link instead of its own group. |
200+
| `titleReplacements` | `{}` | Words to replace in titles made from file and folder names. |
201+
| `titleCase` | `true` | Title-case titles made from file and folder names. |
202+
| `stripTags` | `true` | Remove Markdown Tags syntax (`((tag\|draft))`) from titles. |
203+
| `transformTitle` | none | A function that gets every title before truncation. |
204+
| `sort` | `'name'` | Order for unlisted entries in every folder, e.g. `'date desc'`. See [Sorting](#sorting). |
205+
| `limit` | `0` | Most unlisted entries every folder shows, after sorting. See [Limiting](#limiting). |
206+
| `more` | none | Link text for the landing page when a limit left entries out. |
207+
| `groupTitleFromIndex` | `false` | Label a folder's group with its landing page's title. |
208+
| `verbose` | `false` | Log each folder as it is processed. |
208209

209210
## Titles
210211

‎docs/plugins/blog/index.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ blog:
136136
| `category` | none | Written to the feed and `article:section`. |
137137
| `scope` | `section` | `site` (or `global`) also lists the post on the main blog. See [Scopes](#scopes). |
138138
| `sticky` | `false` | Pinned to the top of its blog pages, above the paginated posts, with a Pinned badge. See [Sticky posts](#sticky-posts). |
139-
| `draft` | `false` | Left out of lists and feeds. The page is still built: use [Exclude](/plugins/exclude/) to hide it. |
139+
| `draft` | `false` | Left out of lists and feeds. To leave drafts out of the build and the menus too, see [Drafts](#drafts). |
140140
| `byline` | the `byline` option | `false` hides this post's byline. |
141141
| `cover` | the `cover` option | `true` shows this post's image under its byline. |
142142
| `postNav` | the `postNav` option | `false` hides this post's newer / older links. |
@@ -168,6 +168,12 @@ blog:
168168
- **Posts on the same day sort by their times**, and a date alone counts as the start of its day. Feeds and social tags use the exact moment.
169169
- **The sidebar and navbar sort the same way.** [Auto Sidebar](/plugins/auto-sidebar/#sorting) and [Auto Navbar](/plugins/auto-navbar#settings) read dates with times too, so `.sort: date desc` puts this afternoon's post above this morning's.
170170

171+
### Drafts
172+
173+
`draft: true` keeps a post out of blog lists, `::: blog-posts` blocks and feeds, but on its own the page is still built and can be opened by its URL. With the `drafts` option on, drafts are listed too, with a Draft badge.
174+
175+
This site goes further: [Exclude](/plugins/exclude/#drafts-in-frontmatter)'s `excludeFrontmatter` option, with `blog.draft` in its list, leaves drafts out of the production build, and the same option in Auto Sidebar and Auto Navbar hides them from the menus. In `vitepress dev`, drafts are built and listed with their badge, for preview.
176+
171177
## Making a blog page
172178

173179
A page with `blog-index` in its frontmatter lists posts. `blog-index: true` is a section blog of the page's own folder:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
title: A draft marked in its frontmatter
3+
draft: true
4+
---
5+
6+
# A draft marked in its frontmatter
7+
8+
This page is in the repository, but not on the published site. Its frontmatter says `draft: true`, and the site's config passes `excludeFrontmatter: ['draft', 'blog.draft']` to Exclude, Auto Sidebar and Auto Navbar for production builds. So it is left out of the build, the sidebar, the navbar, search and the sitemap.
9+
10+
`npm run docs:dev` still builds it, so drafts can be previewed while they are written. Remove `draft: true` and it is published with the next build.

0 commit comments

Comments
 (0)