Draft Posts in Eleventy
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.
Setup
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..gitignoretext.env
-
Create a file
.env
in your project root, and defineDEV
to betrue
..envyamlDEV=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 cancp .env{.example,}
..env.exampleyamlDEV=true
-
Add the
dotenv
dependency. Instructions in thedotenv
docs. In my case, that’scommand linepnpm add -D dotenv
Local-only Drafts
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
---
# …
eleventyExcludeFromCollections: true
permalink: 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]
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.
---
title: This is a draft
draft: true
---
<!-- … -->
Group drafts in collection post lists
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.
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];
});
// …
};
<ul>
{% for post in collections.posts %}
{% for post in collections.posts|collectionOrder %}
<li>
{{ post.title }}
</li>
{% endfor %}
</ul>
Mark drafts in the UI
Posts’ draft
data can be used to surface draft status on the front end:
<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>
Footnotes
Articles You Might Enjoy
-
-
Numbered Code Block Lines in Eleventy with Shiki Twoslash
Bringing in a third-party library for easy, reliable line numbering
-
Highlight Code Block Lines In Eleventy with Shiki Twoslash
Eleventy Markdown code block line highlighting made simple