11ty (eleventy) Tutorial
Eleventy is a static site generator. You can create "templates" (either .html or .njk files) and re-use them. This way you don't have to edit your header in every single page file, etc. Then you execute a command, and Eleventy will generate all your page files for you. These files you can upload to your website as you normally would.
If you want to look at an example project that uses Eleventy, check out my website's repository!
If you have questions or feedback regarding the tutorial, please use the comment section at the end of the page! Don't be shy; I know it's frustrating when you're stuck, and I'd love to help.
Requirements
You need to have Node.js ("node") (version 14 or higher) and npm installed on your computer. Your project needs a package.json
. You need to be able to use a command line.
Read my npm tutorial to find out how to do all of these things.
I also highly recommend using a program for coding that has a built-in terminal, such as VSCode.
In your terminal, check your installed version of node and npm:
node -v
should print20.17.0
or highernpm -v
should print10.8.2
or higher
If your version isn't up-to-date enough you simply need install the latest Node.js on your device.
Installation
In your project, execute the following command to install eleventy as a dev dependency for your project:
npm install --save-dev @11ty/eleventy
You should now see "@11ty/eleventy" in the list of your dependencies in your package.json
file.
If you're using VSCode, I highly recommend installing the Extension "Better Nunjucks" to get syntax highlighting for Nunjucks code.
Configuration
In the root of your project, create a file called .eleventy.js
(don't forget the period at the start of the filename!).
This file is where we will put all our configuration (settings) for Eleventy. Basically, here we tell it how we want it to work.
The most minimal version of this file looks like this:
module.exports = function (eleventyConfig) {
// This will stop the default behaviour of foo.html being turned into foo/index.html
eleventyConfig.addGlobalData("permalink", "{{ page.filePathStem }}.html");
return {
dir: {
input: "content",
output: "public",
},
};
};
input
defines the name of the folder in which your templates and pages will go. This is the folder in which you will make your changes. In this example I called it "content", but you can choose any name you like (one common name would be "src" - short for "source").
output
defines the name of the folder into which the finished html files will be generated. In this example I called it "public", but you can choose any name you like (common names are "public" and "dist" - short for "distribution"). Eleventy doesn't delete files, so this folder can also contain other things, such as your images or other assets. Essentially, the contents of the "public" folder will be what you upload to your website. Everything else (such as the input folder) is only necessary for development, and therefore does not need to be uploaded.
Here is an example of a larger configuration file, with explanations:
module.exports = function (eleventyConfig) {
// This makes the eleventy command quieter (with less detail)
eleventyConfig.setQuietMode(true);
// This will stop the default behaviour of foo.html being turned into foo/index.html
eleventyConfig.addGlobalData("permalink", "{{ page.filePathStem }}.html");
// This will make Eleventy not use your .gitignore file to ignore files
eleventyConfig.setUseGitIgnore(false);
// This will copy this folder to the output without modifying it at all
eleventyConfig.addPassthroughCopy("content/assets");
// This defines which files will be copied
eleventyConfig.setTemplateFormats(["html", "njk", "txt", "js", "css", "xml", "json"]);
// This defines the input and output directories
return {
dir: {
input: "content",
output: "public",
},
};
};
Minify (Optional)
If you want, you can tell Eleventy to minify your HTML files, e.g. by removing unneeded whitespaces. That means their file size will be smaller, which means they need less space on your server and that they load faster.
First, you have to install html-minifier-terser with the command npm install html-minifier-terser
.
Now, add this to your configuration file (.eleventy.js
):
// at the very top of the file:
const htmlmin = require("html-minifier-terser");
// in module.exports:
eleventyConfig.addTransform("htmlmin", function (content) {
if ((this.page.outputPath || "").endsWith(".html")) {
let minified = htmlmin.minify(content, {
useShortDoctype: true,
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
});
return minified;
}
return content;
});
These are just the options I recommend. Here is the full list of available options.
(Re-)start eleventy, and it should already work! (You should be able to see that the generated HTML files now have a smaller file size.)
If you get an error it might be because you have errors in your HTML code, such as spare <s or >s. Once you fix them all it should work. If you want a part of your code to be ignored by the minifier completely, add <!-- htmlmin:ignore -->
before and after it, like so:
<!-- htmlmin:ignore -->
<p>Hewwoo ⸜(。> ᵕ < )⸝♡ </p>
<!-- htmlmin:ignore -->
Nunjucks
In this tutorial I will show you how to use Nunjucks to create templates. (There are also other templating languages you can use with Eleventy.) Eleventy will turn our Nunjucks templates into normal HTML files. You don't need any additional things to write Nunjucks code, but if you're using VSCode i recommend installing the Extension "Better Nunjucks" to get syntax highlighting for Nunjucks code.
You can use Nunjucks in your .html
files (located in your input folder) without a problem. Alternatively, you can also create a Nunjucks file by giving the file the extension .njk
. This is necessary for some files (see later). .njk
files can contain normal HTML code, don't worry.
For a full documentation on what you can do with Nunjucks and how, go here. Here are some of the most important things:
Setting a variable:
{% set myText = "Hello World!" %}
{% set showHeader = true %}
Outputting a variable:
{{ myText }}
Outputting a variable (without escaping HTML):
{{ myHTML | safe }}
If:
{% if showHeader %}
<header>...</header>
{% endif %}
If/Else:
{% if hungry %}
I am hungry
{% elsif tired %}
I am tired
{% else %}
I am good!
{% endif %}
For Loop:
{% for item in items %}
<li>{{ item.title }}</li>
{% else %}
<li>This would display if the 'item' collection were empty</li>
{% endfor %}
Note: Logical operators like &&
, ||
and !
as you know them from JavaScript do not exist in Nunjucks, you need to use the words "and", "or" and "not", e.g.:
{% if myVariable1 and myVariable2 %}...{% endif %}
{% if myVariable1 or myVariable2 %}...{% endif %}
{% if not myVariable %}...{% endif %}
Comparisions work the same as in JavaScript (e.g. ==
, !=
, <=
...)
Custom Filters (Optional)
Filters are ways to change or use information in Nunjucks. You can write your own custom filters, which opens up a lot of possibilites. This is what the Nunjucks code looks like - the filter's name is added after a pipe (|):
{{ myVariable | doSomethingWithIt }}
To define a custom filter you add something like this to your configuration file (.eleventy.js):
eleventyConfig.addFilter("doSomethingWithIt", function (value) {
if (!value) return "";
return value.toLowerCase() + " :)";
});
(This is just a simple example of course.)
If your eleventy watcher is running, make sure to restart it after making this or any other change in the configuration file.
Assuming myVariable
has the value "Hello World", the output of {{ myVariable | doSomethingWithIt }}
will be: hello world :)
Using Data (Optional)
The variables you use can come from several different data sources, such as:
- that very file (
{% set myText = "Hello World!" %}
) - the "front matter" of that file (see later) (
myText = Hello World
) - a data file in the
_data
subfolder of your input folder.
I will now describe data files in detail.
In your input folder (in my example that is the "content" folder), create a subfolder with the name _data
. In there, you can create JSON files (.json
) with data. You can then reference that data by using the file name as a variable name in Nunjucks.
If you don't know JSON syntax: don't worry. It's pretty much self-explanatory if you know JavaScript. As usual,[]
are arrays and{}
are objects.
Example: changelog.json
[
{ "date": "2024-12-07", "text": "Created page" },
{ "date": "2024-12-08", "text": "Bugfixes" },
]
Usage in Nunjucks:
<ul>
{% for item in changelog %}
<li>{{ item.date }}: {{ item.text }}</li>
{% endfor %}
</ul>
(This would output a list of all objects in the changelog.json
file.)
There are also other ways to get data. If you're interested read the documentation.
Partials (Optional)
You can create .njk
files and include them in your pages like so:
<header>
{% include 'headerImage.njk' %}
{% include 'menu.njk' %}
</header>
These files must be located in a _includes
folder in your input folder. (In my example this would be content/_includes
.)
Templates
Step 1: The template
Template files must be located in a _includes
folder in your input folder. (In my example this would be content/_includes
.)
A template file needs to have the file extension .njk
, but besides that it can look like a totally normal HTML file. You can (and should) use Nunjucks code to output things that vary from page to page, such as page title, etc. (Like so: <title>{{ title }}</title>
) We will define the values of those variables in the page files in the next step.
Wherever you want the content of the page to be, type {{ content | safe }}
Step 2: The page
Whether your page file is an .html
or a .njk
file doesn't matter - You can use templates in the same way.
All we need to do is the so-called "front matter". It's a section at the top of the file that looks like this:
---
layout: myTemplate.njk
title: About Me
showSelfie: true
---
(You also need the two ---)
This example defines three variables and gives them values. For text variables (strings) the quotation marks are optional. This would work just as well: title: "About Me"
.
You can choose the names of the variables, except for layout
. layout
always defines the filename of the layout you want to use.
The rest of the file is normal HTML (with some Nunjucks code if you want).
Tip: You can even create other types of files, such as .xml files, with Nunjucks. Simply create the file with the extension
.njk
but add thepermalink
variable to the front matter, e.g.:permalink: "rss.xml"
, which would turn that file intorss.xml
. (Here is a complete example on how to generate an RSS file with Eleventy using a data file.)
Example
content/_includes/myTemplate.njk
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ title }}</title>
<script src="{{ nesting }}main.js"></script>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta content="utf-8" http-equiv="encoding" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="{{ nesting }}favicon.ico" rel="icon" type="image/x-icon" />
</head>
<body class="{{ bodyClass }}">
<header>Header</header>
<main>
{{ content | safe }}
</main>
<footer>Footer</footer>
</body>
</html>
content/about-me.html
(or .njk
):
---
layout: myTemplate.njk
title: About Me
bodyClass: about-me-page
nesting: "../"
---
<h1>About Me</h1>
<p>Here is the main content of your file...</p>
The generated file would be public/about-me.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Me</title>
<script src="../main.js"></script>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta content="utf-8" http-equiv="encoding" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="../favicon.ico" rel="icon" type="image/x-icon" />
</head>
<body class="about-me-page">
<header>Header</header>
<main>
<h1>About Me</h1>
<p>Here is the main content of your file...</p>
</main>
<footer>Footer</footer>
</body>
</html>
Pay close attention to the
nesting
variable I defined. This is a good way of making sure that the relative paths in the template work for all pages, no matter the folder depth of the page.In a page file in the root folder (such as
index.html
, your home page) the value would be./
, in a page 1 folder deep it would be../
, in a page 2 folders deep it would be../../
and so on.Keep in mind that the paths are relative to your output folder. This means, in the example above, main.js and favicon.ico are both in the output folder:
public/main.js
andpublic/favicon.ico
.
Extending files
An alternative to using Eleventy templates with front matter (as described in the previous step) is to extend an njk template. This will give you more freedom than the simple templates as described earlier. However, it is often not really necessary. I highly recommend not doing this unless you actually have to.
Show me how
This is only possible in .njk
files and you don't need the "front matter" described earlier.
Blocks defined in the extended file can be overwritten by re-defining the blocks in the other file.
Here is an example:
myTemplate.njk
(which defines the title block and gives it default content):
...
<header>
{% block title %}<h1>Hello World!</h1>{% endblock %}
</header>
...
about-me.njk
(which extends the template and overwrites the content of the title block):
{% extends 'myTemplate.njk' %}
{% block title %}<h1>About Me</h1>{% endblock %}
would output:
...
<header>
<h1>About Me</h1>
</header>
...
Running Eleventy
Execute this command in your terminal to run Eleventy: npx @11ty/eleventy
This will generate your .html files based on the files (such as templates) in your input directory. You have to execute this command after every change you make in your input folder. To preview your website, use the files in the output folder or the web server (see below).
You can also make Eleventy 'watch' your files for changes, so that you don't have to manually execute the command after every change:
npx @11ty/eleventy --watch
watches your filesnpx @11ty/eleventy --serve
watches your files and starts a web server
If you do this, whenever you save a change, Eleventy will generate the html files anew. If you're using the web server (aka viewing your website at localhost, e.g. http://localhost:8080/) the page will even refresh for you automatically.
Defining a custom command (Optional)
In your package.json
file, you can define custom commands. Here is an example:
This would make the command npm run build
execute npx @11ty/eleventy
:
{
...
"scripts": {
"build": "npx @11ty/eleventy"
}
}
I highly recommend this because it's less of a handful to type out.
You can even combine the eleventy command with other commands you might be using, such as webpack:
{
...
"scripts": {
"build": "npx @11ty/eleventy & webpack",
"watch": "npx @11ty/eleventy --serve & webpack --watch",
}
}
(These will run both the Eleventy and the Webpack commands at the same time.)
Done!
Your project structure will look a bit like this:
content/ <-- your input folder
_data/
myData.json <-- a data file you can access as a variable
_includes/
myTemplate.njk <-- a template you can re-use
header.njk <-- a file you can include in another one
about/
about-me.html <-- this is where you make changes
public/ <-- your output folder
about/
about-me.html <-- generated automatically, don't edit this
.eleventy.js <-- the configuration file
REMEMBER:
- Do not edit the generated files (in the output folder), as they will be overwritten the next time you run Eleventy. Only make changes in the input folder.
- Run
npx @11ty/eleventy
after every change (or let Eleventy watch your files and do this for you) to see the changes. - The contents of the output folder are what you upload to your website.
If you want to look at an example project that uses Eleventy, check out my website's repository!
Links
Here are some links to official documentation & other helpful stuff.
I spend hours of my free time writing these tutorials that I publish for free. If you'd like to say thanks, please share this tutorial with others and/or buy me a coffee!
Comments
Leave your questions, problems, suggestions, requests and thanks here!
To share very long code or error messages with me, please upload it to pastebin (or a similar site) and include the link. This is to make sure you don't hit the max character limit on comments.