Sam
Roelants
165 Avenue Michel-Croz, 74400 Chamonix, France
+33 656 704 307
github.com/​sroe­lants
← Back

Configuring a blog with Eleventy

The first thing one no­tices when look­ing at the Eleventy doc­u­men­ta­tion is that there is­n’t re­ally much there. It’s about as bare bones as it gets: no com­pli­cated data mod­els, no strict di­rec­tory hi­er­ar­chies. Eleventy takes all files from one folder, com­piles them, and gen­er­ates a folder of out­put files. Learn­ing how to use it was, in a word, a breeze.

Installing Eleventy and ba­sic us­age

Let’s get started by in­stalling eleventy with npm (or your Node pack­age man­ager of choice)

npm install --save-dev @11ty/eleventy

Eleventy will by de­fault take any files it rec­og­nizes in the cur­rent folder, and out­put processed HTML files in a new sub­folder _site. We can change this be­hav­iour on the com­mand line by pass­ing along some flags. The fol­low­ing com­mand would use npx to run eleventy on all Markdown (.md) and Nunjucks (.njk) files in the di­rec­tory src/, and write the out­put to dist/

npx @11ty/eleventy --input=src --output=dist --formats=md,njk

Instead of pass­ing these flags on every run, we can set them in Eleventy’s global con­fig file .eleventy.js, in the pro­jec­t’s root di­rec­tory. This is where we con­fig­ure all of Eleventy’s be­hav­ior.

module.exports = function(eleventyConfig) {
return { dir: { input: "src", output: "dist" }}

Templates and lay­outs

A lot of peo­ple seem to like Eleventy for its sup­port for all the big tem­plat­ing en­gines. This means that mi­grat­ing a blog from Jekyll (that uses Liquid templates) to Eleventy is a breeze: you can sim­ply keep your old blog posts writ­ten us­ing Liquid and start writ­ing newer posts in any other tem­plat­ing syn­tax you’d pre­fer. By de­fault, Eleventy fig­ures out which tem­plat­ing en­gine to use by look­ing at the file ex­ten­sion (ie. mark­down for .md files, Nunjucks for .njk files, etc…), though the user can fine tune this in the con­fig.

Templates al­low you to dy­nam­i­cally cre­ate con­tent pages de­pen­dent on some ex­tra en­vi­ron­ment vari­ables. These vari­ables are usu­ally stored in the YAML-for­mat­ted front mat­ter of the tem­plate:

---
section: blog
title: Configuring a blog with Eleventy
author: Sam Roelants
tags:
- draft
- web
- static site generator
- eleventy
- configuration
description: ""
---
...

Of course, it would be hor­ri­bly re­dun­dant to have to in­clude all the lay­out markup (ie, the html) in every sin­gle tem­plate file cor­re­spond­ing to a sin­gle post. This is where lay­outs re­ally shine. Eleventy al­lows you to cre­ate a generic page with all the lay­out, and pop­u­late that with posts stored in tem­plate files. By de­fault, all these lay­out files are stored in an _includes/ folder along with the rest of your site’s sources. Getting a page to use a given lay­out is as sim­ple as adding a layout: mylayout.njk to the front mat­ter.

File or­ga­ni­za­tion

Unlike many other sta­tic site gen­er­a­tors, Eleventy places no con­straints on your di­rec­tory tree. You get to or­ga­nize your pro­ject how­ever you see fit, with­out eleventy get­ting in the way. For this blog, I have some­thing re­sem­bling the fol­low­ing:

./
src/
_includes/
assets/
sass/
images/
posts/
drafts/
dist/

You can de­fine folder-wide front mat­ter vari­ables that ap­ply to all tem­plate files found in that folder. These are stored in a json file car­ry­ing the same name as the folder, eg. posts.json in the folder posts. All blog posts use the same lay­out-file, have the same url for­mat and in­clude the post tag so they can be col­lected later on. Instead of adding all these tags to the front mat­ter of every tem­plate, I have the fol­low­ing file set up:

(src/posts/posts.json:)

{
"permalink": "/blog/{{ title | slug }}/index.html",
"layout": "post.njk"
"tags":
- post
}

Filters and short­codes

This is where us­ing a tem­plat­ing lan­guage, rather than plain HTML, re­ally starts to shine. Filters are func­tions that can process the en­vi­ron­ment vari­ables. Some are built in (eg. to es­cape a string or turn it into a slug), but users can eas­ily drop their own fil­ters into the .eleventy.js con­fig file. If I wanted to use an es­caped ver­sion of the ti­tle some­where in the page, I sim­ply use {{title | safe }}. In the last sec­tion, I gen­er­ated a slug to form the perma­link to my blog posts us­ing {{url | slug }}. If I want to use the date, that is stored as a JS Date ob­ject — and hence is ut­terly use­less to us — we could write a fil­ter readable so that we could dis­play the date as {{ date | readable }}. We can drop this func­tion in eleventy.js as fol­lows:

module.exports = function(eleventyConfig) {
const moment = require('moment');

eleventyConfig.addFilter("readable", function(date) {
return moment(date).format("LL");
});
});
};

Shortcodes are the sec­ond con­struc­tion where the power of tem­plat­ing re­ally be­comes ap­par­ent. They are ba­si­cally reusable snip­pets of code that can be pre­de­fined. I use the same for­mat for some post meta­data (the post­ing date and tags) in sev­eral places through­out this blog. Shortcodes al­low me to de­fine the fol­low­ing snip­pet in eleventy.js:

eleventyConfig.addShortcode("metadata", 
function(date, tags) {
let tagMarkup = "";
for (tag of tags) {
if (tag != "post") {
tagMarkup +=
'<a href="/thoughts/tags/' +
tag + '" class="tag">#' + tag + "</a>";
}
}

return `
<span class="post-metadata">
Posted on
<span class="posted-date">
<time datetime="${moment(date).toISOString()}">
${moment(date).format("LL")}
</time>
</span>
${tagMarkup}
</span>`
;
});

While I’m not en­tirely sold on the idea of hard­cod­ing part of my markup in some con­fig file, it does make for easy us­age: Anywhere I want to print this pre­for­mat­ted meta­data, I sim­ply drop in a {% metadata date, tags %}.

Tweaking the de­tails

The Eleventy core is pretty lean. Because it runs on node.js, all the bells and whis­tles it lacks are avail­able to us through count­less npm pack­ages. This be­ing a fairly mod­est and sim­ply blog, I feel like I’ve only scratched the sur­face of what’s pos­si­ble.

Syntax high­light­ing

The de-facto stan­dard for syn­tax high­light­ing code blocks is Prism.js. Because we are pre-ren­der­ing every­thing, we don’t have to rely on Prism pars­ing our code blocks on the fly on the client side. Instead, we can sim­ply pass it over all our files dur­ing the build step! Little op­ti­miza­tions like this add up and re­duce bun­dle size and ren­der time. Right now, I am us­ing the eleventy-plugin-syntaxhighlight pack­age, but I feel like sim­ply us­ing the Prism.js node pack­age di­rectly would give me more flex­i­bil­ity. Adding the plu­gin is as easy as adding:

const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");

eleventyConfig.addPlugin(syntaxHighlight);

Math ren­der­ing with KaTeX

For years, the stan­dard for ren­der­ing math on­line was the Mathjax li­brary. De­spite it be­ing a mam­moth of a li­brary, it is used on rep­utable math sites like Math Overflow and math.stack­ex­change. The ren­der­ing is fairly slow and there’s many is­sues with con­tent jump­ing all over the place as the ren­der­ing changes the el­e­ment di­men­sions. Also, this be­ing a pre-gen­er­ated site, I wanted some­thing that could eas­ily be run dur­ing the build process. The lit­tle doc­u­men­ta­tion I found about run­ning MathJax on the server­side made it seem fairly hacky and quite the ef­fort, so I opted against it.

A new player on the field is KaTeX, de­vel­oped by the guys at Kahn University. It is far more lean and has an ex­cel­lent server-side ren­der­ing API. What’s more, be­cause we have the en­tire NPM ecosys­tem at our ser­vice, adding KaTeX sup­port was as sim­ply as in­stalling the KaTeX plu­gin for Markdown-it (the de­fault mark­down parser Eleventy uses).

  let markdownIt = require("markdown-it");
let markdownItKatex = require("markdown-it-katex");
let options = {
html: true,
breaks: false,
linkify: true
};

let markdownLib = markdownIt(options).use(markdownItKatex);
eleventyConfig.setLibrary("md", markdownLib);

And presto! We have math ren­der­ing on our blog! Inline math like eiπ=1e^i\pi = -1 or f:ABf: A \twoheadrightarrow B seems to be work­ing fine with­out mess­ing up the line height too much. Block level math is also pretty straight­for­ward. Nothing holding me back from cal­cu­lat­ing some com­plex in­te­grals the Cauchy way:

γf(z)dz=2πiiRes(f,ai).\oint_\gamma f(z)\,dz = 2\pi i\sum_i\mathrm{Res}(f, a_i).

Static as­sets

I tend to keep a di­rec­tory in my file hi­er­ar­chy for sta­tic as­sets (ie. css, javascript files and im­ages) that don’t need to be processed by eleventy. Eleventy comes with a built-in func­tion that copies fold­ers to the pro­duc­tion di­rec­tory whole­sale. Unfortunately, globs are not yet al­lowed, so each di­rec­tory that is to be copied must be listed ex­plicitely.

eleventyConfig.addPassthroughCopy("src/assets/css");
eleventyConfig.addPassthroughCopy("src/assets/js");
eleventyConfig.addPassthroughCopy("src/assets/images");

Concluding thoughts

There’s much more to be said still on what’s pos­si­ble us­ing Eleventy’s lightweight but flex­i­ble ar­chi­tec­ture, but I’ll keep it at this. Setting everything up was a breeze, de­spite the some­times lack­ing doc­u­men­ta­tion. Eleventy as an SSG has gar­nered so much at­ten­tion in the last year that there is no lack of blog posts on how to set up the finer de­tails. Of course, this is only the start, and I can’t wait to see how much more can be cus­tomized.