Getting started with Eleventy in 11 minutes
Tags: eleventy, staticsite, jamstack, javascript
Published: 2020-10-31 15:11:15

---
title: Getting started with Eleventy in 11 minutes
published: true
description: This article will show you how to build a static site in 11 minutes using Eleventy
tags: ["eleventy", "staticsite", "jamstack", "javascript"]
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/4cvimfhyrpd7mfipxtz6.png
---

In this article, we will explore Eleventy, a fast and simple static site generator written in Node.js.

We will do so in a very practical way by incrementally building a simple example website from scratch.

During this exercise, we will learn some of the basic concepts to master with Eleventy like templates, layouts, data files and even how to use data from external sources like third-party REST APIs.

All the code from this article is available on GitHub at [lmammino/11ty-sample-project](https://github.com/lmammino/11ty-sample-project).


## Bootstrapping the project

Let's dive just right in by creating a new project called `11ty-sample-project`:

```bash
mkdir 11ty-sample-project
cd 11ty-sample-project
npm init -y
```


## Installing Eleventy and building our first site

Eleventy can be installed using npm. You can install it globally in your system, but I personally prefer to install it as a development dependency for a given project. This way you can have different projects using different versions of Eleventy if needed.

```bash
npm i --save-dev @11ty/eleventy
```

Now let's create an index file for our Eleventy project:

```bash
echo "# My sample Eleventy website" > index.md
```

At this point, we are ready to run Eleventy:

```bash
node_modules/.bin/eleventy --watch --serve
```

Of course, for simplicity, we can put this script in our `package.json`:

```javascript
// ...
"scripts": {
  "start": "eleventy --watch --serve"
},
// ...
```

So now we can run Eleventy more easily by just running:

```bash
npm start
```

We can now see our site at [localhost:8080](http://localhost:8080).

![My sample Eleventy website](https://dev-to-uploads.s3.amazonaws.com/i/zysnte9kx0w3e5pmh7l5.png)


## Create a custom config file

Eleventy follows some default conventions, but it is also quite flexible and allows you to change these defaults.

This is convenient if, for whatever reason, you prefer to change the default folder structure or the supported templating languages and much more.

In order to provide our custom configuration to Eleventy we have to create a file called `.eleventy.js` in the root folder of our project:

```js
module.exports = function (config) {
  return {
    dir: {
      input: './src',
      output: './build'
    }
  }
}
```

With this specific configuration, we are redefining the input and output folders for the project. All our source files will be inside `src` and the generated files will be in `build`.

Now let's actually create the `src` folder and move `index.md` file into `src`. We can also remove the old build folder (`_site`):

```bash
mkdir src
mv index.md src
rm -rf _site
```

Finally, make sure to restart Eleventy. Our site has not changed, but now all the generated files will be stored in `build`.

You might have noticed that in our configuration file, the function definition receives an argument called `config`. This is something that allows for more advanced configuration. We will be discussing an example shortly.


## Nunjucks templates with frontmatter

So far we have been using only markdown files to define the content of our static site. Let's now create a [Nunjucks](https://mozilla.github.io/nunjucks/) template called `src/page.njk` with the following content:

```jinja
<!DOCTYPE html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <title>A new website</title>
</head>
<body>A sample page here</body>
</html>
```

Once we save this new file, the build will generate a new page that we can visualize at [localhost:8080/page](http://localhost:8080/page).

![A sample page generated using Nunjucks](https://dev-to-uploads.s3.amazonaws.com/i/g7kbekgwbwa2d5f3kywm.png)

Interesting enough, now if we change anything in the source template, the browser will automatically refresh showing us the result of the latest changes.

This is because, once we have a complete HTML structure, Eleventy will inject a [BrowserSync](https://browsersync.io/) script in the page, that will reload the page automatically on every change. Note that this code is injected into the HTML pages only at runtime when receiving the pages through the development web server, it is not actually present in the generated HTML. For this reason, you don't have to do anything special to generate a build ready to be deployed to your production server. In any case, if you only want to generate a build, without spinning up the development web server, you can do so by running `eleventy build`.

But let's talk a bit more about templates now.

In Eleventy, markdown (`.md`), Nunjucks (`.njk`) and many other file types ([see the full list](https://www.11ty.dev/docs/languages/)) are called **templates**. These files can be used as a skeleton to generate pages. Eleventy will automatically search for them in our source folder and, by default, it will generate a page for each and every one of them. We will see later how we can use a single template to generate multiple pages.

Templates can have a **frontmatter** part at the top which can be used to define some additional metadata.

The frontmatter part must be specified at the top of the file and is delimited by `---` as in the following example:

```
---
name: someone
age: 17
---
Rest of the file
```

Inside the frontmatter, the metadata is specified using YAML and you can even have nested properties if that makes sense for your specific use case.

In our project, I think it makes sense to use frontmatter to add a `title` attribute to our new template:

```jinja
---
title: A NOT SO NEW website
---
<!DOCTYPE html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <title>{{ title }}</title>
</head>
<body>A sample page here</body>
</html>
```

Note how data in the frontmatter part can be used straight away in our template using the interpolation syntax of the templating language of choice (`{{ variableName }}` in the case of Nunjucks).


## Layouts

What if we want all the generated pages (or just some of them) to have the same HTML structure? Also, if we like to use markdown, ideally, we would like the generated HTML to be wrapped in a properly constructed HTML layout that includes a `head` and a `body` section.

With Eleventy, we can do this by using **layouts**.

Layouts can be stored inside the `_includes` directory in the source folder. This is a special folder. In fact, Eleventy won't be generating pages for markdown, Nunjucks or other templates files available inside this folder. Eleventy will also make sure that all the files placed here will be easily available to the templating language of our choice.

Let's create our first layout in `src/_includes/base.njk`:

```jinja
---
title: My default title
---
<!DOCTYPE html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <title>{{ title }}</title>
</head>
<body>
  <main>
    {{ content | safe }}
  </main>
</body>
</html>
```

Note that the special variable `content` is where the main content (coming from a template) will be placed. We use the filter `safe` because we want the HTML coming from the template to be applied verbatim (no escaped text).

Without `safe` the HTML coming from a template containing `<h1>Hello from Eleventy</h1>` will be rendered as follows:

```html
<!-- ... -->
<body>
  <main>
    &lt;h1&gt;Hello from Eleventy&lt;/h1&gt;
  <main>
</body>
```

Which, of course, is not what we want...

Now we can go back and edit `index.md` to use our base template:

```markdown
---
layout: base
---

# Hello from Eleventy

This is a simple Eleventy demo
```

Now we can try to reload our index page and check the source code of the page in the browser!

![A page generated using a base layout](https://dev-to-uploads.s3.amazonaws.com/i/4zqoc4tod2kjo2xupdpq.png)


## Copying static files

What if we want to add some style to our generated pages? How do we add CSS? Of course, we could easily add inline CSS in our templates and layouts, but what if we want to include an external CSS file?

Let's create `src/_includes/style.css`:

```css
html, body {
  background-color: #eee;
  margin: 0;
}

main {
  box-sizing: border-box;
  max-width: 1024px;
  min-height: 100vh;
  padding: 2em;
  margin: 0 auto;
  background: white;
}
```

Now how can we make sure that this CSS file gets copied to the build folder?

Let's edit the config `.eleventy.js`:

```js
module.exports = function (config) {
  config.addPassthroughCopy({ './src/_includes/style.css': 'style.css' })

  // ...
}
```

Invoking the `addPassthroughCopy` function is essentially telling Eleventy that, for every build, the given source file will need to be copied (as it is) to the given destination in the build folder.

Check out the build folder, and we will see `style.css` there! If it's not there, try restarting the Eleventy build.

We can now update our default layout to reference this stylesheet by adding the following code in the `head` block:

```html
<link rel="stylesheet" href="/style.css"/>
```

This will essentially inform the browser to load the CSS style from our `style.css` file when the page is loaded.

![Added some stylesheet to our base layout](https://dev-to-uploads.s3.amazonaws.com/i/pp7ak6qu6wy7ix07wlgx.png)

You can use the same technique to copy client-side JavaScript files, images, videos or other static assets into your build folder.


## Global data files

When building static sites, we generally have some "global" data that we want to be able to reference in our templates and layouts.

Just to deal with a very simple example, I like to keep all the site metadata (author information, copyright information, domain name, google analytics ID, etc.) in a dedicated file.

Let's create a file with some generic site information in `./src/_data/site.js`:

```js
'use strict'

module.exports = {
  author: 'Luciano Mammino',
  copyrightYear: (new Date()).getFullYear()
}
```

The folder `_data` is another special data folder. Every `js` and `json` file inside it will be pre-processed and made available using the file name (`site` in this case) as the variable name.

Now we can update our base layout and add a footer:

```jinja
{# ... #}

<main>
  {{ content | safe }}
<hr/>
<small>A website by {{ site.author }} - &copy; {{ site.copyrightYear }}</small>
</main>

{# ... #}
```

![The website now has a footer populated by data from a data file](https://dev-to-uploads.s3.amazonaws.com/i/5lf8mhh7vger1z14xsqx.png)


## The collection API

When building static sites, it is very very common to have content coming from files that need to be somehow grouped into logical categories. For instance, if it is a blog, we will have a collection of blog posts and we can even group them by topic.


Let's try to create a few sample blog posts:

```bash
echo -e "---\ntitle: Post 1\nlayout: base\n---\n# post 1\n\nA sample blog post 1" > src/post1.md
echo -e "---\ntitle: Post 2\nlayout: base\n---\n# post 2\n\nA sample blog post 2" > src/post2.md
echo -e "---\ntitle: Post 3\nlayout: base\n---\n# post 3\n\nA sample blog post 3" > src/post3.md
```

Now let's add the tag "posts" in the frontmatter of every blog post:

```yaml
---
tags: [posts]
---
```

Now if we want to display all the posts in another template we can do that by accessing the special variable `collections.post`. For instance we can add the following to `src/index.md`:

```jinja
{% for post in collections.posts %}
- [{{ post.data.title }}]({{ post.url }})
{% endfor %}
```

![Showing a list of posts in the home page](https://dev-to-uploads.s3.amazonaws.com/i/j4dbjl5i4hlum2u0jqr9.png)

For every tag in our templates, eleventy will keep a collection named after that tag. We can then access the list of templates in that collection by using `collections.<name of the tag>`.

There is also a special collection named `collections.all` that contains every single template. This can be used [to generate sitemaps](https://dev.to/loige/create-a-sitemap-for-your-eleventy-website-25h) or ATOM feeds.

{% link loige/create-a-sitemap-for-your-eleventy-website-25h %}

For every element in a collection, we can access the data in the frontmatter of that template by using the special `.data` attribute. In our example, we are doing this to access the `title` attribute. There are also special attributes such as `url` or `date` that we can use to access additional metadata added by Eleventy itself.


## Using dynamic content

Now, what if we want to get some data from an external source like a REST API?

That's actually quite easy with Eleventy!

For this tutorial, we can use an amazing FREE API that allows us to access information for all movies produced by [Studio Ghibli](https://en.wikipedia.org/wiki/Studio_Ghibli), which we can find at [ghibliapi.herokuapp.com](https://ghibliapi.herokuapp.com/).

With this API we can, for instance, call `https://ghibliapi.herokuapp.com/films/` to get the list of all the movies.

![Using the Ghibli API to get the list of Studio Ghibli movies](https://dev-to-uploads.s3.amazonaws.com/i/ghwefgu900f1zbb6r2s9.png)

This can be a good API for us and we can try to use Eleventy to generate a new page for every single movie.

Since we want to cache the result of this call, to avoid calling it over and over at every build we can use `@11ty/eleventy-cache-assets`

```
npm i --save-dev @11ty/eleventy-cache-assets
```

Now let's create `src/_data/movies.js`:

```javascript
'use strict'

const Cache = require('@11ty/eleventy-cache-assets')

module.exports = async function () {
  return Cache('https://ghibliapi.herokuapp.com/films/', { type: 'json' })
}
```

Now we can access the `movies` array in any template or layout.


## Creating a page for every movie

Let's create a template called `src/movie-page.md`


```md
---
layout: base
permalink: /movie/{{ movie.title | slug }}/
pagination:
  data: movies
  size: 1
  alias: movie
eleventyComputed:
  title: "{{ movie.title }}"
---

## {{ movie.title }}

  - Released in **{{ movie.release_date }}**
  - Directed by **{{ movie.director }}**
  - Produced by **{{ movie.producer }}**

{{ movie.description }}

[<< See all movies](/movies)
```

There's a lot to unpack here! Let's start by discussing the `pagination` attribute in the frontmatter.

This special attribute tells Eleventy to generate multiple pages starting from this template. How many pages? Well, that depends on the `pagination.data` and the `pagination.size` attributes.

The `pagination.data` attribute tells eleventy what array of data do we want to iterate over, while `pagination.size` is used to divide the array into chunks. In this case, by specifying `1` as size, we are essentially telling Eleventy to generate 1 page per every element in the `movies` array.

When using the [pagination API](https://www.11ty.dev/docs/pagination/) we can reference the current element (in the case of 1 element per page) by specifying an `alias`, which in our case we defined as `movie`.

At this point, we can specify the URL of every page using the `permalink` attribute. Note how we are interpolating the `movie` variable to extract data from the current movie.

If we need to define element-specific frontmatter data, we can do so by using the special [`eleventyComputed`](https://www.11ty.dev/docs/data-computed/) attribute. In our example, we are doing this to make sure that every generated page will have its own title.

If we want to see how one of the pages looks like we can visit [localhost:8080/movie/ponyo/](http://localhost:8080/movie/ponyo/).

![THe page generated for the Ponyo movie](https://dev-to-uploads.s3.amazonaws.com/i/ic0k2u6gdt2f9qpkhcdw.png)

Now we can easily create the index page to link all the movies in `src/movies.md`:

```markdown
---
layout: base
title: Studio Ghibli movies
---

# Studio Ghibli movies

{% for movie in movies %}
- [{{ movie.title }}](/movie/{{ movie.title | slug }})
{% endfor %}
```

![Index page showing all the movies from Studio Ghibli](https://dev-to-uploads.s3.amazonaws.com/i/5ah95rnt9sl55228kau2.png)

Take some time to navigate around and, hopefully, get to know some new movies! 😎


## It's a wrap 🌯

And this concludes our Eleventy tutorial!

In this article we learned about the following topics:

 - How to Install Eleventy and bootstrap a new project from scratch
 - Creating a simple "Hello world" website
 - Providing custom configuration
 - Templates, frontmatter & Layouts
 - Using live reload
 - Copying static files
 - Custom global data
 - The collection API
 - Using dynamic data from external sources
 - The pagination API

There is a lot more we can do with Eleventy, so make sure to check out the [Eleventy official documentation](https://www.11ty.dev/docs/) to learn more.

If you found this article interesting consider following me here, on [Twitter](https://twitter.com/loige) and check out my [personal website/blog](https://loige.co) for more articles.

Also, if you like Node.js consider checking out my book [Node.js Design Patterns](https://www.nodejsdesignpatterns.com/).

[![Node.js Design Patterns book hold by man](https://dev-to-uploads.s3.amazonaws.com/i/gm3sfzusx8s8bhpbpid0.png)](https://www.nodejsdesignpatterns.com/)

Thank you! 👋

PS: special thanks to [Ben White](https://twitter.com/d3v1an7) on Twitter for providing some useful feedback!