juneum
Developer Notes

Draft Posts in Eleventy

February 8, 2022
trisager

If you are using Eleventy to create a blog, you will quickly discover that Eleventy doesn't have a built-in method for marking new posts as drafts. You can work around that by putting your work in progress in a folder outside src/, but it is simple to implement draft functionality yourself using environment variables and Eleventy´s Computed Data feature.

Environment Variables

We want drafts to be visible while we work on a new post locally, but not when the blog is running on the production web server. We can distinguish between the two situations by setting a Node.js environment variable in our build script in package.json:

{
  ...
  "scripts": {
    "start": "npx eleventy --serve",
    "build": "ELEVENTY_ENV=production npx eleventy",
  },
}

During the build, our environment variable will be made available by Node.js' process module in the env property:

process.env.ELEVENTY_ENV; // "production"

We can make environment variables available to our templates using the example given in the Eleventy documentation. Create a JavaScript file in the _data directory with the following content:

module.exports = function () {
  return {
    environment: process.env.ELEVENTY_ENV,
  };
};

Our environment variable is now available as global data (so if you saved the code above as "_data/myProject.js", you can access the environment variable as myProject.environment in your templates).

In this example we just need to know if we are building for production or not, so the above is sufficient. If you need to handle more cases, e.g. for a staging environment, you can create separate npm build scripts that specify different environments, e.g. ELEVENTY_ENV=staging.

Also note that we are setting the environment in our npm build scripts rather than using dotenv or a similar package. This is what you want if are building your production site locally, uploading it with rsync or sftp to a web server that doesn't have Node.js installed. If you are using Vercel, Netlify or the like, look into dotenv.

Computed Data

In order to prevent a draft post from being shown, we need to do two things:

  1. We must prevent the post from being written to the _site folder during the build process, and
  2. We must exclude the post from being included in any collections

According to the Eleventy documentation, if we use permalink: false in the post's front matter, the post won't be written to the output folder. However, that will prevent it from being output in all cases. What we need is to be able to set the value of permalink conditionally, depending on our build environment.

Similarly, we can use eleventyExcludeFromCollections: true to prevent our post from being included in any collections, but again, we need to do this conditionally.

Eleventy has a Computed Data feature that lets us make additional data available to our templates at the end of the data cascade. Computed data can be specified in the post's front matter or using JavaScript data files. In our case we keep our posts in _src/posts, so we'll create a posts.11tydata.js file in that folder with the following content:

// _src/posts/posts.11tydata.js

const isProd = process.env.ELEVENTY_ENV === "production";

const exclude_from_prod_build = (data) => {
  return isProd && data.draft;
};

module.exports = {
  eleventyComputed: {
    eleventyExcludeFromCollections: (data) => {
      if (exclude_from_prod_build(data)) {
        return true;
      } else {
        return data.eleventyExcludeFromCollections;
      }
    },
    permalink: (data) => {
      if (exclude_from_prod_build(data)) {
        return false;
      } else {
        return data.permalink;
      }
    },
  },
};

The code above assumes that we add a draft: true key in the front matter of draft posts. The exclude_from_prod_build function receives the output from the data cascade for each post processed by Eleventy and returns true if the post should be excluded from further processing. In our case we only consider the value of draft from the front matter, but you could specify other conditions, e.g. you could exclude posts with a future publication date by modifying this function.

Finally we export an object with an eleventyComputed key. The corresponding value is another object that holds the computed values of the two properties we are interested in. For a draft post, the computed properties will be:

{
  eleventyExcludeFromCollections: true,
  permalink: false
}

Exactly what we need to keep our drafts out of production builds.

← Back to articles