How the heck do I use Eleventy? The intro guide I wish I had

Julius Tarng
9 min readSep 2, 2020

If you’re like me, you’ve heard a lot about Eleventy, a static site generator that’s been praised by a lot of really awesome people recently. However, if you’re also like me, you found the documentation didn’t map to how I liked to learn. It’s very focused more on the tactical “what commands to run?”, instead of on the “how does the system work?”. I needed to understand the mental model, before getting lost in terminal.

Here’s the guide I wish I had, and I hope it helps you!

Why Eleventy

The Overview page of the docs sold me, but after this page, I was completely lost, thrown into a sea of terminal commands.

I finally got a chance to use it for MakeSpace, as we were preparing the to grow the website from 2 static HTML pages to a blog with many other pages. I chose Eleventy because I liked a lot of the framing around their motivations:

  • It’s framework-less, so you can use the technologies that you’re used to, from templating to any client-side code
  • It’s incremental, so I could use it to gradually shift our site over (although I ended up just fully adopting it for every page because I wanted to play with more of Eleventy’s capabilities)

Generating the site

At a very basic level, Eleventy works like other static site generators. It takes a folder of inputs, processes those input files, and spits out a folder of outputs, which is your final website.

Let’s say you have a folder of HTML and Markdown files, like shown below. If you setup and run 11ty (I like npx @11ty/eleventy --serve, which will load up a hot reloading webserver — I recommend adding it to your package.json scripts ), it will convert each of those files into the final website, by default in a folder called _site.

Note that the non-index files have become folders, with an index.html inside, which provides cleaner URLs: makespacefoundation.org/team instead of makespacefoundation.org/team.html.

A diagram showing a folder of HTML and MD files, going through 11ty, and coming out with a _site folder.

That’s basically how most static site generators work, but 11ty is different in that it’s meant to be super customizable. You can change where it looks for inputs, what specific filetypes you want to process, what files to ignore, and where to export the final website. You can use a mix of templating engines. You can add “filters” to process content in the templates, like linkifying URLs in plain text. You can use JSON or Javascript functions to supply data (including fetching from APIs at build time) that generates page content. The list goes on, and having only spent a few hours with it, I’m sure there’s lots more I don’t know I don’t know.

The docs got very confusing for me because it’s generally unopinionated about how you should set up your website, and doesn’t guide you through how you might think about customizing it to your needs, even though it does comes with some default behaviors. I’m going to walk you through, chronologically, how I ran into these default behaviors and changed them to suit my needs.

Ignoring files with .eleventyignore

One of the first things I ran into was that my README.md in my Github repo was being processed into the final website as makespace.fun/readme. You can ignore specific files and folders just like .gitignore. Simply create a file named .eleventyignore and add the names of the files and folders inside the file, one on each line.

README.md
some_folder_i_want_to_ignore

Customizing Eleventy with the config file: .eleventy.js

Bring unprocessed files along with Passthrough File copy

I then noticed that 11ty didn’t bring my styles and assets over to the final website. By default, it only looks for .html and .md files, and doesn't bring over some important things like a /css or /assets folders. This was my first hurdle!

After a lot of clicking around, I found out about the .eleventy.js configuration file. You can create it pretty easily, and it's where you do a bulk of the customization.

// .eleventy.js in the project root
module.exports = function(eleventyConfig) {
// Customizations go here
}

In order to pass the /css and /assets folders through the Eleventy processor, I needed to add these lines of code that specify Passthrough Copy behavior.

module.exports = function(eleventyConfig) {
// This will copy these folders to the output without modifying them at all
eleventyConfig.addPassthroughCopy("assets");
eleventyConfig.addPassthroughCopy("css");
}

You can even get fancy and change the name and location of the folders during passthrough.

There’s more you can customize, like the templating engines, template locations, or custom filters, and I’ll speak to those in the context of other concepts below.

Templating

Up until now, I wasn’t really getting much of Eleventy’s value. It was cool to see passthroughs work, and the idea of picking out a subset of files to convert to clean URLs was nice — but it was a lot of hassle. Thankfully, templating was next up as I wanted to refactor some shared components between the two pages I had (Home and Team), specifically: the <meta> tags in <head>, nav bar, and footers.

Customizing template engine

In the examples below, I’m going to use the default LiquidJS templating given my background in Jekyll, but you can change the default template engine for markdown and HTML in the .eleventy.js config, using the return syntax if you're using other configs like Passthroughs.

module.exports = function(eleventyConfig) {
return {
markdownTemplateEngine: "njk",
htmlTemplateEngine: "pug"
}
}

A blog post example with layout and includes

Having used Jekyll before, I was familiar with the concept of a layout template, and an include template. Let’s say you had a blog post as a markdown file. You wanted to put in a blog post page template, and the blog post template wants to include a reusable component, like a footer.

hello-09.02.md the content

---
layout: blogpost.liquid
title: Hello! This is my first blogpost
---
# Wow, what a cool blog post
Yes, this is in markdown! Wahoo!...

The --- section at the top is called Frontmatter. It's basically a way to put data about the file so that other files in the templating engine can read it. In this case, I'm telling Eleventy that I want to use a file called blogpost.liquid as the layout to wrap around this blog post, and to provide a variable called title to the layout.

blogpost.liquid the layout

---
title: Default Title
---
<html>
<head><title>{{ title }}</title></head>
<body>
{{ content }}
{% include footer %}
</body>
</html>

Note that this has frontmatter, too! It has a default fallback title, in case you want to use this layout with a file where you don’t specify a title in frontmatter. The {{}} double curly braces let you render data inline, and the {% %} syntax allows you to call Liquid JS tags like include, if, and even for loops.

footer.liquid the include

<footer>
(c) MakeSpace Foundation 2020
</footer>

This will output:

<html>
<head><title>Hello! This is my first blogpost</title></head>
<body>
<h1>Wow, what a cool blog post</h1>
<p>Yes, this is in markdown! Wahoo!...</p>
<footer>
(c) MakeSpace Foundation 2020
</footer>
</body>
</html>

Advanced templating with chaining and include variables

You can even chain layouts (have a layout refer to another layout as its parent) and nest includes. Includes also let you pass variables and data down so you can do things like add a selected style to the currently active nav bar page:

// index.html
{% include nav, current: 'index' %}
// team.html
{% include nav, current: 'home' %}
// nav.liquid
<ul>
<li class="{% if current == 'index' %} current {% endif %}">
<a href="/">
Home
</a>
</li>
<li class="{% if current == 'team' %} current {% endif %}">
<a href="/team">
Team
</a>
</li>
</ul>

I also used frontmatter to specify things like what CSS file to load in the parent layout.

---
css: team.css
---
<link rel="stylesheet" href="/css/{{ css }}">

Customizing include directory

By default, Eleventy will search for a folder called _includes for the templates that you can use. You can easily change this with the dir.includes prop.

Data

So many names to update!

As I went through and templatized my files, I started noticing a lot of information that was statically coded as HTML, which could be more manageable as a JSON file (and potentially being able to query from an API at build time sounded exciting). I ran into this on our Team page, which lists dozens of collaborator names, and was a pain to update.

You can specify data in a lot of ways in Eleventy. There’s a concept called Data Cascading that even specifies the order in which Eleventy will look for data in various places, and how they’ll merge if there are conflicts.

  1. Computed Data
  2. Front Matter Data in a Template
  3. Front Matter Data in Layouts
  4. Template Data Files
  5. Directory Data Files (and ascending Parent Directories)
  6. Global Data Files

Using Directory Data Files to store page-specific data

So, for the use case of the Team page above, I decided to add a .json file inside a team directory (changing team.html to team/index.html in the process so the JSON can live alongside the HTML). Just by creating a team.11tydata.json file in the team/ folder, I was able to access its properties without any additional configuration!

{
coconspirators: ["Name", "Name", ...]
}
{% for person in coconspirators %}
<li>{{ person }}</li>
{% endfor %}

Filters

It was at this point in converting static HTML into JSON data that I ran into an issue — some of the HTML didn’t convert nicely into JSON. Specifically, the Team bios which included links that opened in a new tab. with target=_blank.

I could just move the raw HTML into the JSON file like so:

{
team: [
{
bio: "This person worked at <a href="https://makespacefoundation.org" target=_blank>MakeSpace</a>"
}
]
}

But that felt really verbose and unclean, especially the need to target blank every link. I would rather store this as markdown, and process every link to open in a new tab.

That’s where Filters come in! Eleventy comes with a few, which you can use inline with any data output {{ data | filter }} in a template or markdown or HTML file.

Creating a custom filter

Creating a filter is super easy to do in the config file:

module.exports = function(eleventyConfig) {
let options = {
html: true,
breaks: true,
linkify: true,
}
let md = require("markdown-it")(options);
eleventyConfig.addFilter("markdownify", function(value) {
return md(value);
});
}

Now I can use markdownify as a filter anywhere! In order to add target=_blank to every link I needed to look up some more code to customize the markdown-it package, but the setup looks very similar to the above.

More things to learn

I hope this guide was helpful! These were roughly the steps I went through where I felt like I really grasped what made Eleventy nice to use. We’re going to add some more static pages in the next few days, as well as setting up the blog. It’ll be interesting to play with pagination, collection tags, and all the other blog functionality I haven’t tapped into like plugins for breadcrumb navigation. I also want to move some of the data from a local JSON to a Google sheet so it can be more easily edited by everyone without having to be invited to the private repo. Writing custom shortcodes seems super powerful, too.

Get started with Eleventy here →

Thanks for reading,
Julius Tarng
Twitter: @tarngerine
tarng.com — looking for part time product design / design prototyping work in fall, winter 2020!

--

--