![](/posts/draft-posts-in-eleventy/3MoPiT3mPe-240.jpeg)
Eleventy Markdown code block line highlighting made simple
Last Updated
Draft Eleventy posts are included on the front end in development mode only, not on the production site. When using version control software (VCS) the simplest way to make a post dev-only is to not track it— with Git, for example, do not commit the file. But with that approach you miss out on tracking changes to the draft, and you can’t distinguish drafts from published posts on the front end. Another option is a dedicated drafts’ Eleventy collection. But that approach can require reworking plugins to consume both the published posts’ collection and the drafts’ collection (for example when displaying all or related posts); and, with Eleventy’s path-based collections, publishing will require relocating the post file which can complicate looking at its VCS history. A good third option hinges on adding a new “draft” data field to posts.
Contents
We’ll need a way to distinguish development and production environments. The standard solution is to store an environment-specific variable in an .env file, and to use dotenv
to access the env variable in the site JS.
gitignore the path .env
. .env
files are often used to store sensitive information (read Algolia Search in VuePress Without Joining DocSearch for an example). Here, it will help us distinguish between “dev” and non-“dev” environments.
text
.env
text
.env
Create a file .env
in your project root, and define DEV
to be true
.
yaml
DEV=true
yaml
DEV=true
I like to use an .env.example
file to keep a record of all expected environment variables. I leave out sensitive information; “true” isn’t sensitive, so it’s safe to leave in. Then, if you or someone else clones the project, they can cp .env{.example,}
.
yaml
DEV=true
yaml
DEV=true
Add the dotenv
dependency. Instructions in the dotenv
docs. In my case, that’s
shell
pnpm add -D dotenv
shell
pnpm add -D dotenv
In production, draft should not be written to the file system (the page should not exist) and they should be excluded from collections (keep them out of post archives, “related post” sections, and anything else that uses a collection
).
Setting eleventyExcludeFromCollections: true
in a post’s front matter will exclude it from collections.[1] Setting permalink: false
will keep it from being written to the file system.[2] So one way of marking a post as a “draft” is
md
---# …eleventyExcludeFromCollections: truepermalink: false# …---<!-- … -->
md
---# …eleventyExcludeFromCollections: truepermalink: false# …---<!-- … -->
But if you set eleventyExcludeFromCollections: true
or permalink: false
on any posts for a reason other than its being a draft, keeping track of true drafts will be tough.
The solution is to make that data dynamic. Then draft status and publication date can be used as factors in eleventyExcludeFromCollections
and permalink
.
Use eleventyComputed
for dynamic data.[3] Use a collection-level JS data file to define dynamic data for all posts in a collection.[4]
js
require('dotenv').config(); // or ESM: `import 'dotenv/config'`const dev = process.env.DEV === "true";const now = new Date();/*** If a post is a `draft`, it is for dev mode only.** @param {object} data Post data* @param {boolean} [data.draft=false] Post draft status* @returns {boolean}*/function devOnly(data) {return Boolean(data.draft);}module.exports = {eleventyComputed: {eleventyExcludeFromCollections: data => {if (!dev && devOnly(data)) {return true;}return data.eleventyExcludeFromCollections;},permalink: data => {if (!dev && devOnly(data)) {return false;}return data.permalink;},},// …}
js
require('dotenv').config(); // or ESM: `import 'dotenv/config'`const dev = process.env.DEV === "true";const now = new Date();/*** If a post is a `draft`, it is for dev mode only.** @param {object} data Post data* @param {boolean} [data.draft=false] Post draft status* @returns {boolean}*/function devOnly(data) {return Boolean(data.draft);}module.exports = {eleventyComputed: {eleventyExcludeFromCollections: data => {if (!dev && devOnly(data)) {return true;}return data.eleventyExcludeFromCollections;},permalink: data => {if (!dev && devOnly(data)) {return false;}return data.permalink;},},// …}
Now, setting draft: true
in a post’s front matter will make it a draft, and it will not be part of production builds.
md
---title: This is a draftdraft: true---<!-- … -->
md
---title: This is a draftdraft: true---<!-- … -->
With a bunch of drafts in the hopper, “all posts” lists can be cluttered and significantly different in development from in production. Consider grouping drafts together. The Eleventy docs suggest creating a new collection
with a custom sort[5]; I prefer a custom Eleventy filter.
js
module.exports = function(eleventyConfig) {// …eleventyConfig.addFilter("collectionOrder", (posts) => {const drafts = [];const published = [];for (const post of posts) {if (post?.data?.draft) {drafts.push(post);continue;}published.push(post);}return [...drafts, ...published];});// …}
js
module.exports = function(eleventyConfig) {// …eleventyConfig.addFilter("collectionOrder", (posts) => {const drafts = [];const published = [];for (const post of posts) {if (post?.data?.draft) {drafts.push(post);continue;}published.push(post);}return [...drafts, ...published];});// …}
twig
<ul>{% for post in collections.posts %}{% for post in collections.posts|collectionOrder %}<li>{{ post.title }}</li>{% endfor %}</ul>
twig
<ul>{% for post in collections.posts %}{% for post in collections.posts|collectionOrder %}<li>{{ post.title }}</li>{% endfor %}</ul>
Posts’ draft
data can be used to surface draft status on the front end:
twig
<ul>{% for post in collections.posts|collectionOrder %}<li>{{ post.data.title }}{% "(draft)" if post.data.draft and not post.data.draft == "false" %}</li>{% endfor %}</ul>
twig
<ul>{% for post in collections.posts|collectionOrder %}<li>{{ post.data.title }}{% "(draft)" if post.data.draft and not post.data.draft == "false" %}</li>{% endfor %}</ul>
Highlight Code Block Lines In Eleventy with Shiki Twoslash
Eleventy Markdown code block line highlighting made simple
Numbered Code Block Lines in Eleventy with Shiki Twoslash
Bringing in a third-party library for easy, reliable line numbering
Comparing docs site builders: VuePress vs Starlight
How do these two frameworks measure up?