Static site generators are very à la mode right now and with the JAMStack becoming a more-than-viable alternative for many web projects these days, it’s no wonder!
JAMStack or not, static site generators (SSGs) offer some major advantages over more traditional CMSs, and when it comes to SSGs, there are plenty of options to choose from. Jekyll, Hugo, Gatsby, Next.jst, Sapper,… Oh my!
I was recently shopping around to see what would be my best static site generator option for a project of mine called Spiral11. I was first leaning towards Gatsby because it packs some modern goodies like image optimization, but then I looked further at Eleventy and fell in love.
11ty is easy to use, doesn’t get in your way and spits out exactly what you put in, so there’s no surprise or hidden code bloat. At its most basic, 11ty just compiles files it finds from your working directory into static HTML files. Plus, since it’s written in JavaScript, you gain access to the whole of npm in terms of packages you can use in your project.
Let’s take a tour and see how it works…

Eleventy Site Setup

Start by installing Eleventy globally on your machine using npm or Yarn:

# with npm
$ npm install -g @11ty/eleventy

# with Yarn
$ yarn global add @11ty/eleventy

Now you can run the eleventy command in any directory that contains valid template files.
For example, say we have a directory called best-site-ever, with an index.md file in it:
index.md

## Chomp Chomp **Chomp** 🐊🐊🐊

You can now run eleventy in that directory, you’ll see that Eleventy creates a _site directory with an index.html file in it that contains what we expect:
_site/index.html

<h2>Chomp Chomp <strong>Chomp</strong> 🐊🐊🐊</h2>

Speaking of template languages, you have plenty of options. You can use Markdown, Nunjucks, Liquid, Mustache,… And you can mix and match, so you can have some files written in Liquid and some in Nunjucks and Eleventy will handle everything just fine.

eleventy command options

The eleventy command also accepts a few useful flags. For example:

–watch: Rewrite output files when any of your project files change.
–serve: Serve the outputted site via a local web server and watch for changes.
–dryrun: Test out the processing without actually outputting any files.
–output: Specify a different output location.

That means that very often, when working locally, you’ll want to serve and rebuild on changes with the following command:

$ eleventy --serve

Directory Structure

With Eleventy you’re not only free to choose your favorite template language, but you can also choose your own directory structure.
Say we have another markdown file in our project, but this time it’s in two nested sub-directories:
/one/two/blog-post.md

One day *I will write* my 1st blog post!

Now if we run eleventy, the output in _site will look like this:

📁 _site/
  index.html
  📁 one/
    📁 two/
      📁 blog-post/
        index.html

So you’ll notice that the directory structure is kept, that the file name is used as the slug/URI and that Eleventy creates a directory for each outputted template with an index.html file within it which contains the HTML output.
What if we wanted our files organized in directories that are reflected differently in the final output? Easy, we can just specify the permalink for a particular template inside a front matter section at the top.
/one/two/blog-post.md

---
permalink: '/blog-post/'
---

One day *I will write* my 1st blog post!

Just run eleventy again and you’ll now see that now blog-post appears at the root level in _site. This way, specifying a permalink allows you full flexibility.

Front matter

Speaking of front matter, you can put all sorts of metadata in it, which will then be available to layouts (more on that later). It’s in the front matter, for example, that you’ll specify things like the title, meta description/excerpt and tags for a post, and most of the keys can be named whatever you’d like.
Here’s an example to illustrate:
/one/two/blog-post.md

---
title: "My first blog post 😊"
date: 2020-04-02
excerpt: "This post talks about how one day I'll write a 1st post."
permalink: '/blog-post/'
emotion: happy
tags:
  - blog
  - getting-started
---

One day *I will write* my 1st blog post!

Collections

I won’t go too deep on the topic here, and I invite you to read the docs to learn more, but Eleventy uses the tags you provide in the front matter to populate collections, which can be iterated over in other templates like, say, category pages.
There’s also a special collection called all which by default includes every single post. With the all collection, you may end up with something like a sitemap that includes posts/pages you don’t want, so there’s an easy way to opt out of having a post or page included in collections:

---
eleventyExcludeFromCollections: true
---

Layouts

So far what we’ve done is just output HTML from the markdown files we’ve created. Most of the time though such markdown files should be wrapped in a layout to provide things like the boilerplate HTML, header, navigation bar, sidebar and footer.
By default layout templates should live in the _includes directory.
Let’s create some sample layouts using Nunjucks syntax. One will be the default layout with our HTML boilerplate and the other one will be our blog layout:
_includes/default.njk

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ title }} | My Site</title>
</head>
<body>
   {{ content | safe }}
</body>
</html>

_includes/blog.njk

---
layout: default
---

<h1>{{ title }}</h1>
<div>
  Published on {{ date }}
</div>

<article>
  {{ content | safe }}
</article>

As you can see, a layout can call another layout and our layouts can access the data that’s in the front matter of our regular template files.
We can use a layout by specifying it in the front matter of any of our content templates:

---
layout: blog.njk
title: "My first blog post 😊"
date: 2020-04-02
excerpt: "This post talks about how one day I'll write a 1st post."
---

One day *I will write* my 1st blog post!

Eleventy Configuration

So far we’ve done everything without even touching a configuration file, and it goes to show the simplicity of using Eleventy. But as your website gains in complexity, you’ll surely want to be able to set some configuration options. You can do that in an .eleventy.js file at the root of your project.
Here’s a sample configuration file which copies our static assets to the output directory and specifies a different output directory name (public):
.eleventy.js

module.exports = eleventyConfig => {
  // Copy our static assets to the output folder
  eleventyConfig.addPassthroughCopy('css');
  eleventyConfig.addPassthroughCopy('js');
  eleventyConfig.addPassthroughCopy('images');

  // Returning something from the configuration function is optional
  return {
    dir: {
      output: 'public'
    }
  };
};

You can do all sorts of things in your config file like minifying the HTML output, using plugins and configuring shortcodes. Again, I invite you to look at the docs to learn more about what can be configured. But if you start to get overwhelmed at first, just remember that by default Eleventy doesn’t even need a config file and can do most of the heavy lifting without one.

Using a Starter

So far we’ve been creating a site from scratch to see how things work, but often you’ll want to get started with a good base that’s already configured and/or styled in a way that you like. You can browse through a list of 11ty starters here.
I personally really like the Skeleventy starter, which uses Tailwind CSS for styling and is configured with PurgeCSS to get rid of unused styles in production. If you’re starting a blog, the official Eleventy blog starter would also be a great place to start.

Learning More

✨✨ That’s it for this brief introduction, but I invite you to learn more by checking out these resources:

The official docs
This Learn With Jason episode with Zach Leatherman, the creator of Eleventy