Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

How I teach Eleventy from scratch

Eleventy is my favourite static site generator and I’ve been using it for ~6 years now to build almost all of my sites, including this one. I’ve also written a few things about it and spoken in THE Eleventy Meetup about it.

That has led to a few people asking me for help to get started in understanding how it works and how they can build their own site. Instead of starting with a starter kit, I’ve decided to start from scratch with them to help understand the fundamentals of how it works so they can build on top of that knowledge even if they then choose to kickstart their site with a starter kit.

What I really like about Eleventy is that you can build things so incrementally. You can start with a single file and single command and every step of the way, your outcome is a working website. Let’s take a look at how!

The audience to which I’ve been going this workshop through with have been software developers who are already familiar with how to run command line tools, install packages and so on, so it’s not an absolute beginner class.

In this workshop, I won’t teach you how to build a website. The HTML, CSS and Javascript bits nor the actual content. If you’re new to all that, I recommend starting from MDN’s Learn web development guide. This workshop focuses on Eleventy parts of combining all those things in an effective way.

Prerequisites

You need to have Node.js, npm and npx installed to be able to follow along. In addition, a code editor is needed and familiarity with using terminal helps a lot.

Once everything is installed, create a new folder for your project.

Step 1: index.md

In a nutshell, static site generators are tools that turn markdown files into html files. They do a lot of other things as well but that’s a good baseline to start with.

So create a file called index.md and add to it:

Hello world!

Then run npx @11ty/eleventy --serve in terminal. You should see something along the lines of:

[11ty] Writing ./_site/index.html from ./index.md (liquid)
[11ty] Wrote 1 file in 0.36 seconds (v3.0.0)
[11ty] Watching…
[11ty] Server at http://localhost:8081/

(if npx asks if you want to install @11ty/eleventy, say yes)

Then open the server at the given localhost URL in a browser and you should see your first Eleventy website: a white page with black text that says “Hello world!”

A browser window on

Welcome to the world of building static sites with Eleventy.

While this website isn’t very useful yet, I like how it’s all you need to get started with Eleventy. You don’t need to generate 15 folders and 100 files to get a text to show up on a website. Thanks to npx, you don’t even a package.json file to manage the project yet.

💡 In this step, you learned how to create a markdown file and run Eleventy to turn it into a web page.

Step 2: Let’s write a blog post

A great reason to build your own static website is to start blogging! Blogging is fun and beneficial to you in many ways and Eleventy is a good engine to build one with.

To start a blog, let’s create a new folder called blog/ and create our first blog post blog/my-first-post.md:

# My first post

I decided to start blogging and build my blog with [Eleventy](https://11ty.dev)

Then run npx @11ty/eleventy --serve again it’s not already running and open the browser.

We don’t yet have any links to the post itself from the index page but you can manually navigate to it by adding /blog/my-first-post/ to the end of the URL.

A website with a heading My first post and a paragraph of text with a link titled Eleventy.

The blog post written in Markdown was processed into HTML with headings and links.

Blog post metadata

You can add metadata in YAML format into front matter of your blog posts. Let’s add some now to our post.

---
title: My first post
date: 2024-11-05
tags: blog
---

# My first post

I decided to start blogging and build my blog with [Eleventy](https://11ty.dev)

Front matter is added by adding a block that starts with three dashes (—-- ) and ends with three dashes (—--) to the very beginning of your file. These will not be visible in the post itself but can be used later on with templating.

💡 In this step, you learned how to create a blog post, add a bit of metadata and navigate to it on your site.

Step 3: Blog index

To make it easier for our readers to find our blog posts, let’s create a listing of our blog posts in our index page. To do that, we need to switch our index.md to another templating language that supports a bit more features. Eleventy supports quite a few, I like using Nunjucks because it’s similar to Jinja that I’m used to from Django world.

  1. Rename index.md to index.njk

Change the contents of index.njk to:

<h1>My blog</h1>

<ul>
  {% for post in collections.blog %}
  <li><a href="{{post.url}}">{{post.data.title}}</a></li>
  {% endfor %}
</ul>

We’re doing a couple of new things here.

First, we loop over a collections.blog which is a collection of all files that have a tag of “blog”. In the previous step, we added tags: blog to our blog post which tells Eleventy to add it to this particular collection.

Next, inside the loop, we access post.url which is internal metadata created by Eleventy as it processes through all the files. Anything inside our custom front matter, we access through post.data object. In this case, we want the link text to be the title of the post.

Now run the server and check the results. We have our blog post in a list!

Website with heading My blog and a bullet list with single item, a link with title My first post

It’s a bit lonely though so let’s give it a friend.

Create blog/my-second-post.md with

---
title: My second post
date: 2024-11-06
tags: blog
---

# Second post in two days

I'm having a blogging streak! Let's go.

With this file saved, our listing now shows both blog posts. We’re now officially bloggers!

Website with heading My blog and a bullet list with two items, a link with title My first post and another with title My second post

💡 In this step, you learned about using Nunjuck templating language to create a list of posts and how to access data from individual posts. You also learned that by specifying tags , you can add files to Eleventy collections.

Step 4: Layouts and includes

So far, our styling and layout has been very barebones. One key feature in modern static site generators is the ability to include other bits of code in different places. This separates them from writing all your HTML code manually by hand. Write it once and use it in many places!

Let’s make a base layout for our website.

  1. Create a new folder _includes/ and inside it, _includes/layouts/ .
  2. Create a new file _includes/layouts/base.njk

Base template

Inside base.njk, write

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

This is our starting point for creating a layout. You can add all your <head> tags inside it and they will then be spread out to everywhere where we use this layout.

The {{ content | safe }} tells our template to put all of the content of a file that’s using this template here. The safe filter (we’ll talk about filters soon, I promise) tells it to render any HTML we pass it.

Let’s use it

Tell our index.njk to use it. At the start of the file, add

---
layout: layouts/base.njk
---

Your page doesn’t change much but if you look at the source for the page in your browser, you can see it was wrapped into the scaffolding we wrote into the template.

Template for a blog post

Next, let’s create another template for our blog post.

Create _includes/layouts/post.njk and write:

---
layout: layouts/base.njk
---

<article>
  <h1>{{title}}</h1>
  <time>{{date}}</time>

  {{content|safe}}
</article>

Notice how our layout is using another layout! We can chain these templates into each other, making it easier to compose smaller pieces and combine them as we need.

Here, we can access our front matter data directly so we can use {{ title }} and {{ date }} like that.

Next, we need to tell our blog posts to use this layout.

In both of the files in blog/, add the following to the front matter:

layout: layouts/post.njk
Website with two headings with the same content My first post, a date in long format Wed Nov 06 2024 02:00:00 GMT+0200 (Itä-Euroopan normaaliaika) and a single line of text.

We now have our title twice there. Let’s remove the title from the actual post contents in our markdown files by removing the # My first post and # My second post respectively in the two blog post files.

Website with a heading My first post, a date in long format Wed Nov 06 2024 02:00:00 GMT+0200 (Itä-Euroopan normaaliaika) and a single line of text

Much better!

The date looks a bit too specific but let’s worry about that a bit later.

💡 In this step, you learned how to create HTML layout templates and how to apply them to other files.

Step 5: Eleventy configuration

We’ve gotten quite far without defining anything for Eleventy ourselves. Now it’s time to take a look at how we can configure Eleventy to help make our work easier.

Let’s start by creating eleventy.config.mjs into the root of the project. In Eleventy, we define our configuration using Javascript.

CSS and image assets

Until now, every file we’ve created has been processed by Eleventy in some way on its way to the _site folder where our final static site lives in. Next, we want to add images and CSS styles that we want copied as-is to _site.

Create a new folder assets/ in the root. Inside, create subfolders css/ and images/.

A side tip: if you want to create multiple folders like this in command line, you can do mkdir -p assets/{css,js}. It’s called brace expansion and it’s very handy!

Next, let’s tell Eleventy that we want anything in assets/ to be copied as-is to _site/assets every time a new build is created. In eleventy.config.mjs add:

export default async function (eleventyConfig) {
  eleventyConfig.addPassthroughCopy("assets");
}

A few things of note here. The first line is how we tell Eleventy this is the function we want to use for our configuration. It’s in ESM format that is supported in Eleventy 3.0.0 onwards.

It provides us eleventyConfig object that is our API to all things we want to configure. We use addPassthroughCopy to define which folders and files we want to be passed as-is without trying to process them further. In this case, everything inside assets/ folder.

Let’s fix that date format with a filter

In our previous step, when we passed a date to Nunjucks templating, we got a very specific format out. Sometimes it can be useful but often we want something more user friendly. Let’s say we want to show our blog post dates as “[month written out] [date], [year]”.

We can create a custom filter to do it. In Eleventy, filters are functions we can run from our templates and they will transform the input into an output format we like.

First, outside the function we already have, let’s create a new function called blogDate:

function blogDate(input) {
  return `${new Date(input).toLocaleString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric",
  })}`;
}

We then register this as a filter inside the configuration function with:

eleventyConfig.addFilter('blogDate', blogDate)

The first argument is the name of the filter, as used in templates and the second is the function to be called.

To use this filter in our templates, we change in _includes/layouts/post.njk:

// from
<time>{{ date }}</time>
// to
<time>{{ date | blogDate }}</time>

Note that when you make changes to the Eleventy configuration files, you sometimes may need to restart the server (with npx @11ty/eleventy --serve). So if your filters are not being applied, try restarting it and see if that does the job.

There are a few built-in filters in Eleventy and you can make as many as you wish yourself. A very handy built-in filter is log which prints the contents into the terminal, helping debug when things don’t work.

💡 In this step, you learned how to create an Eleventy configuration file, how to pass folders and files as-is to the output folder and how to create and apply custom filters.

Step 6: Plugins

The final thing I want you to know about is the plugin ecosystem. You can use plugins written by different people to enhance your Eleventy.

One that I want to introduce early on is the official RSS plugin so your blog will be set up for people to follow via RSS feed from the beginning.

To be able to install plugins, we need to set this site up with npm.

Run

npm init -y

in the root of the project. This will initialise the project and create a package.json file.

We can then install Eleventy and the plugin into this project with

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

Inside eleventy.config.mjs, at the top, import the plugin:

import { feedPlugin } from "@11ty/eleventy-plugin-rss";

and inside the configuration function, activate it:

eleventyConfig.addPlugin(feedPlugin, {
		type: "rss",
		outputPath: "/feed.xml",
		collection: {
			name: "blog",
			limit: 10,     // 0 means no limit
		},
		metadata: {
			language: "en",
			title: "My very cool blog",
			subtitle: "I'm writing about stuff",
			base: "https://example.com/",
			author: {
				name: "Juhis",
				email: "", // Optional
			}
		}
	});

When you run Eleventy, it will create a new file, _site/feed.xml with your blog as its contents. You can tweak the configuration or create the RSS template manually by following the documentation.

💡 In this step, you learned how to install and activate a plugin and made your blog better for your readers by providing a RSS feed.

Conclusion

There’s a lot that goes into building a website. This short workshop is intended to give a basic fundamental understanding of how Eleventy works by building a site from scratch. If you don’t want to build everything from scratch for your real site, you can take a look at the starter projects built by the community.

You should also join our wonderful community. We have an active Discord server, a lovely meetup and a great newsletter that gathers resources so you can keep on learning and being inspired by other people.