The author selected the Internet Archive to receive a donation as part of the Write for DOnations program.
Eleventy (also known as 11ty) is a static site generator (SSG) for building websites. It was launched in 2017 by Zach Leatherman as a JavaScript-based alternative to Jekyll, one of the first mainstream SSGs, which is written in Ruby. Eleventy has gained a reputation as one of the most flexible and performant options for building static websites, leading to steadily rising adoption rates in the Jamstack ecosystem.
It’s important to note that Eleventy is not a JavaScript framework, and it does not include any client-side JavaScript. It takes template files specified in HTML, Markdown, or your choice of templating language, and outputs a complete, static website ready to be deployed to a web server of your choice.
While most other SSGs are restricted to just one templating language, Eleventy supports multiple templating languages, such as HTML, Liquid, Markdown, Nunjucks, Handlebars, moustache, EJS, Haml, Pug, etc., and you can even combine them in the same project. This flexibility is one of the things that makes Eleventy stand out from its competition.
In this tutorial, you’ll develop a static website from scratch with Eleventy and deploy it to DigitalOcean’s App Platform for free.
To complete this tutorial, you will need:
npm
, the Node.js package manager. For installation directions, follow How to Install Node.js and Create a Local Development Environment. Note that the latest version of Eleventy at the time of writing (v0.12.1) requires Node.js v10 or newer.Unlike competitors such as Jekyll and Hugo, Eleventy does not provide a way to scaffold a new project, so you’ll need to create a regular Node.js project, and then add Eleventy as a dependency.
The first step is to launch the terminal on your computer, create a new directory somewhere on your filesystem, and change into it as shown below.
- mkdir eleventy-blog
- cd eleventy-blog
At the root of the eleventy-blog
directory, initialize the project with a package.json
file with npm init -y
, and install Eleventy as a development dependency by passing the -D
flag to the install
subcommand.
- npm init -y
- npm install -D @11ty/eleventy
Once the Eleventy package is installed, inspect the contents of your project directory with ls
. It will contain a package.json
file, a package-lock.json
file, and a node_modules
directory.
The output should look similar to this:
Outputnode_modules package-lock.json package.json
Open the package.json
file in your favorite text editor, then replace the existing scripts
property with the highlighted lines below.
{
"name": "eleventy-blog",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "eleventy",
"start": "eleventy --serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@11ty/eleventy": "^0.12.1"
}
}
There are two scripts here: build
for building the website files, and start
for running the Eleventy web server on http://localhost:8080
.
Save the file, and then run the command below to build the website:
- npm run build
The output should look similar to this:
Output> eleventy-blog@1.0.0 build
> eleventy
Wrote 0 files in 0.04 seconds (v0.12.1)
The output indicates that you haven’t created any files yet, so there is nothing to build. In the next step, you’ll begin adding the necessary files that are needed for the website.
The final directory structure of the project you’ll be working on is shown below. You’ll start from an empty directory, and incrementally add new features until you arrive at this structure.
.
├── about
│ └── index.md
├── css
│ └── style.css
├── _data
│ └── site.json
├── _includes
│ ├── layouts
│ │ ├── base.njk
│ │ ├── page.njk
│ │ └── post.njk
│ └── nav.njk
├── index.njk
├── node_modules
├── package.json
├── package-lock.json
├── posts
│ ├── first-post.md
│ ├── second-post.md
│ └── third-post.md
└── _site
├── about
│ └── index.html
├── css
│ └── style.css
├── index.html
└── posts
├── first-post
│ └── index.html
├── second-post
│ └── index.html
└── third-post
└── index.html
In this step, you created a Node.js project and added Eleventy as a dependency. In the next step, you’ll choose a templating language.
For the purpose of this tutorial, we’ll use the Nunjucks template, a common choice for many Eleventy projects. (Depending on your preference, you could also choose a different templating language.)
In the root of your project directory, create an index.njk
file and open it in your text editor. Add a “Hello world” message to the file as shown below, then save the file.
<h1>Hello, world!</h1>
Once saved, run the build command again. It will convert the index.njk
file to an index.html
file and output it into a new _site
directory at the root of the project.
- npm run build
The output should look similar to this:
Output> eleventy-blog@1.0.0 build
> eleventy
Writing _site/index.html from ./index.njk.
Wrote 1 file in 0.08 seconds (v0.12.1)
At this point, you can view the website in the browser by starting a development server at http://localhost:8080
as shown below.
- npm start
The output should look similar to this:
Output> eleventy-blog@1.0.0 start
> eleventy --serve
Writing _site/index.html from ./index.njk.
Wrote 1 file in 0.08 seconds (v0.12.1)
Watching..
[Browsersync] Access URLs:
-----------------------------------
Local: http://localhost:8080
External: http://172.29.217.37:8080
-----------------------------------
UI: http://localhost:3001
UI External: http://localhost:3001
-----------------------------------
[Browsersync] Serving files from: _site
If you wish to use a different port, you can specify it through the --port
option, as shown here:
- npm start -- --port 4040 to set a different port
The --
separator in the command above is used to distinguish the parameters passed to npm
command itself from those passed to the script. After starting the development server, open http://localhost:8080
in your web browser to see the site in action. You can exit the server at any time by pressing Ctrl + C
on your keyboard.
In this step, you used Nunjucks as the templating language and began building a website. In the next section, you’ll learn about layouts in Eleventy and create a homepage for the website.
In order to make your project more flexible and scalable from the start, you’ll need to create a base template that will be applied to all the pages of the site. Conventionally, this is called the base
layout, and it needs to go into a layouts
directory nested within an _includes
directory. Create the _includes/layouts
directory using the command below:
- mkdir -p _includes/layouts
In the _includes/layouts
directory, create a base.njk
file and open it in your text editor. Copy and paste the following code into the file. This is basic HTML5 boilerplate that will serve as the foundation for all the pages on the website.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
</head>
<body>
<header>
<h1>{{ title }}</h1>
</header>
<main>
{{ content | safe }}
</main>
</body>
</html>
The contents in double curly braces are Nunjucks variables that will be replaced accordingly when a derivative page is being built. The {{ title }}
variable will be supplied through the page’s front matter block while the {{ content }}
will be replaced with all incoming page content that is not part of the front matter. The latter is piped through the safe
filter to prevent it from being escaped.
Return to the index.njk
file in your project root, and modify it as shown below:
---
title: Homepage
layout: layouts/base.njk
---
<h1>Welcome to this brand new Eleventy website!</h1>
The contents on either side of the triple dashes constitute the front matter of the file, while the rest of the file is what will be passed to your layouts as its content
. In the front matter, the title
and layout
of the file are specified accordingly.
If your development server is still running, head over to your site’s localhost URL to view the changes, or start the server first with npm start
before attempting to view it in a web browser.
As you can see from the above screenshot, the base.njk
template has taken effect on the homepage.
In this step, you added a base template for the pages of your site, and created a homepage. However, it doesn’t yet have any styling beyond the browser defaults. In the next section, you’ll improve the design of the website by adding a navigation menu.
The _includes
directory is where you’ll place the different components of the website. The contents of this directory are partial files that can be placed in your layout files to facilitate reuse. In this section, you’ll create the navigation menu as a partial, and include it in the base
layout.
In the _includes
directory, create a new file called nav.njk
. Open it in your editor and populate it with the code below. It is the markup for the top navigation bar, and it includes the title of the site as well as links to the homepage and a yet-to-be created “About” page.
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
<div class="navbar-start">
<div class="navbar-item has-text-weight-bold">
My Eleventy Blog
</div>
</div>
<div class="navbar-end">
<a href="/" class="navbar-item">
Home
</a>
<a href="/about" class="navbar-item">
About Me
</a>
</div>
</nav>
Save and close the nav.njk
file, and open the base template file (_includes/layouts/base.njk
) in your editor. Go ahead and include the new navigation partial in this file through the include
syntax shown below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ title }}</title>
</head>
<body>
<header>{% include "nav.njk" %}</header>
<main>
{{ content | safe }}
</main>
</body>
</html>
In the browser, the site should look like this:
When you check the home page once again, the navigation menu should show up just like in the screenshot above. The title of the site “My Eleventy Blog” is hardcoded into the nav
partial, but this is suboptimal because it’s likely that you will repeat the title elsewhere on the site, and changing it later becomes tedious since you’ll then have to find each place it was used.
A better approach is to supply this value through a global data file. These are JSON files placed in a _data
directory at the project root that provide global data accessible to all template files. At the project root, create a _data
directory, followed by a site.json
file within it. Open site.json
in your text editor and specify the site’s title using the code below.
{
"title": "My Eleventy Blog",
"url": "https://example.com/",
"language": "en-US",
"description": "A simple blog with awesome content"
}
At this point, you can save and close the file, then return to the nav.njk
file in the _includes
directory and replace the hardcoded site title with the appropriate variable.
. . .
<div class="navbar-item has-text-weight-bold">
{{ site.title }}
</div>
. . .
The site should look exactly the same as before, but this small change makes setting and updating global data much easier. One thing to note about global variables is that they are scoped to the name of the JSON file, which is why we used {{ site.title }}
above. You can create other data files as needed, and use them in your templates. For example, you can have an author.json
file that contains your personal details such as your name, bio, and links to your social media profiles. Such data could then be accessed through variables (such as {{ author.bio }}
) on any page of the website.
Return to your index.njk
file at the project root and update its contents as shown below so that it uses the site title and description:
---
title: Homepage
layout: layouts/base.njk
---
<section class="hero is-medium is-primary is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">{{ site.title }}</h1>
<h2 class="subtitle">{{ site.description }}</h2>
</div>
</div>
</section>
In the browser, the site should look like this:
In this step, you added a navigation menu to the website. However, the site is using default styling. In the next section, you’ll style the website using the Bulma CSS framework, which provides flexbox-based frontend components for building responsive websites.
At the moment, Eleventy does not recognize CSS files for auto-inclusion in the build directory, so a few extra steps are needed to get this working. Specifically, you’ll need to create a stylesheet directory, and ensure that it is copied over to the build output (_site
) when the site is built. You’ll also need to ensure that modifying a stylesheet triggers a rebuild and automatic refreshing in the web browser. You can achieved this by creating a configuration file for Eleventy.
At the project root, create a css
folder followed by a style.css
file within it. Open style.css
and import the Bulma CSS framework by using the code below:
@import "https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css";
Save the file.
Next, create an .eleventy.js
file in your project root. This is the configuration file for Eleventy, similar to _config.yml
files in Jekyll projects. Note that this file will be hidden in your filesystem since it’s prefixed with a period. You’ll need to use ls -a
to get it to show up when listing the directory’s contents.
Open .eleventy.js
in your text editor and paste the following to include the css
directory in the build, and also to watch the folder for changes:
module.exports = function (eleventyConfig) {
// Copy the `css` directory to the output
eleventyConfig.addPassthroughCopy('css');
// Watch the `css` directory for changes
eleventyConfig.addWatchTarget('css');
};
At this point, you need to stop the server with Ctrl+C
, and start it again with npm start
before the changes take effect. You’ll need to do this every time you modify the configuration file.
If you check the site in your browser right now, you won’t notice any changes. That’s because the stylesheet has not yet been linked in any template. Go ahead and add it to the base.njk
template as shown below.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/style.css" />
<title>{{ title }}</title>
</head>
<body>
<header>{% include "nav.njk" %}</header>
<main>
{{ content | safe }}
</main>
</body>
</html>
After saving the file, the styles should kick in immediately.
In this step, you added styling to the website using the Bulma CSS framework. In the next step, you’ll expand the site by creating an “About” page.
At the moment, there is a link to a non-existent “About” page in the navigation menu. You’ll change that by creating a unique layout for all static pages, and afterward, the “About” page itself. In the _includes/layouts
folder, create a page.njk
file. This will be the layout for all static pages on the site.
Open the new file in your editor and populate it with the code below. The front matter layout
property is used to indicate that the page
layout should inherit from the previously created base.njk
template. This is known as layout chaining, and it allows us to reuse a template while adding unique elements that are specific to the new template, which helps avoid unnecessary repetition of basic site structures.
---
layout: layouts/base.njk
---
<article class="page-layout">
<div class="container">
<div class="columns">
<div class="column is-8 is-offset-2">
<div class="content mt-5">
<h1>{{ title }}</h1>
{{ content | safe }}
</div>
</div>
</div>
</div>
</article>
Now that you have a page layout, you can create the “About” page. To do this, create a directory at the project root called about
, and add a new index.md
markdown file within it.
Add the following code into the file:
---
title: About Me
layout: layouts/page.njk
---
I am a person that writes stuff.
After saving the file, go to https://localhost:8080/about
. The page should load correctly with the specified layout.
Creating other pages, such as a contact page or newsletter page, can be done in the same way: create a directory with the name of the page, then add an index.md
file at the root of the new directory. You can also use an HTML or Nunjucks file instead of Markdown if you prefer.
In this step, you created a unique layout for static pages and added an “About” page to the site. In the next section, you’ll create and process blog posts on an Eleventy website.
Creating a blog post is very similar to creating a page. You’ll start by creating a directory called posts
at the project root to keep all posts.
However, you will create a different layout for posts. It’s going to be similar to the layout for pages
, but you will include a date to differentiate it. In a real-world project, it’s likely that you’ll want different layouts for posts and pages, so it’s good practice to create a new layout for each one.
In the _includes/layouts
directory, create a new post.njk
file and open it in your text editor. Paste the code below into your post.njk
layout file.
---
layout: layouts/base.njk
---
<section class="page-layout">
<div class="container">
<div class="columns">
<div class="column is-8 is-offset-2">
<article class="content mt-5">
<h1 class="title">{{ title }}</h1>
<p class="subtitle is-6">
Published on: <time datetime="{{ page.date }}">{{ page.date }}</time>
</p>
{{ content | safe }}
</article>
</div>
</div>
</div>
</section>
Similar to the page.njk
template, the post template extends the base template with additions that make sense for posts (such as the date of publication).
To use this template, create a new file in your posts
directory called first-post.md
, and open it in your text editor.
Paste the following contents into the first-post.md
file:
---
title: My First Blog Post
description: This is the first post on my blog
tags: post
date: 2021-06-19
layout: layouts/post.njk
---
You’ll find this post in your `posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `eleventy --serve`, which launches a web server and auto-regenerates your site when a file is updated.
Save the file, then head over to http://localhost:8080/posts/first-post
in your browser. Notice how the URL corresponds to the location of the file in the project (excluding the extension). This is how URLs are handled by default, but they can be changed to some other format through the permalink key.
The post is displayed correctly, but notice how the date is currently formatted. This date format is difficult for users to read, but Eleventy provides no built-in formatting options for dates, unlike most other SSGs. This means that you have to use an external package to get a more human readable format in Eleventy.
In this step, you created a unique layout for blog posts and added a blog post to the site. In the next section, you’ll create a custom filter that helps with date formatting.
Eleventy supports filters for transforming content in various ways. For example, the safe
filter used earlier prevents the escaping of HTML content, and there are other built-in ones like slug
for transforming text into URL-friendly strings. You can also add your own custom filters that can be used in any template. These customizations can be made through the configuration file.
Go ahead and add a universal filter for formatting dates so that it can be used in the post template. Start by installing Luxon, a lightweight JavaScript library for date formatting:
- npm install --save luxon
Afterward, open the .eleventy.js
config file, and update its contents as follows:
const { DateTime } = require('luxon');
module.exports = function (eleventyConfig) {
// Copy the `css` directory to the output
eleventyConfig.addPassthroughCopy('css');
// Watch the `css` directory for changes
eleventyConfig.addWatchTarget('css');
eleventyConfig.addFilter('readableDate', (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat(
'dd LLL yyyy'
);
});
};
The highlighted lines describe how to add a custom filter to Eleventy. First, you need to import whatever objects you need from any external libraries. The DateTime
type from Luxon provides several methods for formatting purposes.
Adding a filter involves calling the addFilter()
method provided by the eleventyConfig
argument. It takes the filter name as its first argument, and the callback function is what will be executed when the filter is used. In the above snippet, the filter is called readableDate
, and the callback function is used to format a date object using the provided date tokens. This will yield a date in the following format: 19 Jul 2021.
Save the config file and restart the server so that the changes take effect. Then use the readableDate
filter in the post template as shown below to format the post date according to the specified layout.
. . .
<p class="subtitle is-6">
Published on: <time datetime="{{ page.date }}">{{ page.date | readableDate }}</time>
</p>
. . .
Once you open the post in your browser, you’ll notice that the date formatting has been updated.
In this step, you added a filter to change the date formatting on blog posts. In the next section, you’ll display a list of posts on the homepage, as is conventional on most personal blogs.
To make it easier for visitors to your site to discover the posts on the blog, it’s a good idea to list them on the homepage. You will use Eleventy’s collections feature in order to implement this functionality.
Before you proceed to update the index.njk
file, you’ll need to create at least three additional posts to showcase on the homepage. You can copy the first post into new files, and change the title and description for each of them. Once you’re through with that, update your index.njk
file as shown below:
---
title: Homepage
layout: layouts/base.njk
---
<section class="hero is-medium is-primary is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">{{ site.title }}</h1>
<h2 class="subtitle">{{ site.description }}</h2>
</div>
</div>
</section>
<section class="postlist mt-3 pt-3">
<div class="container">
<h2 class="title has-text-centered mt-3 mb-6">Recent posts</h2>
<div class="columns">
{% for post in collections.post | reverse %}
{% if loop.index0 < 3 %}
<div class="column">
<div class="card">
<header class="card-header">
<p class="card-header-title">
{{ post.data.title }}
</p>
</header>
<div class="card-content">
<div class="content">
{{ post.data.description }}
</div>
</div>
<footer class="card-footer">
<a href="{{ post.url }}" class="button is-fullwidth is-link card-footer-item">Read article</a>
</footer>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</section>
The for
loop construct in the above snippet is from the Nunjucks templating language, and its one of the ways to iterate over a collection in Eleventy. It starts with {% for post in collection.post | reverse %}
and ends with {% endfor %}
. The post
collection is created by Eleventy and consists of any page that has its tags
front matter property set to post
.
The reverse
filter is used here so that the iteration starts from the most recent post instead of the default order, which puts older posts first. Within the loop, the output of the collection is limited to three items, and the post
local variable is used to access the title, description, and URL of each post in the collection.
After saving the file, go to the homepage in the browser to see the results. It should look similar to the screenshot below.
In this step, you created additional blog posts and used Eleventy’s collections feature to list them on the website’s homepage. You now have a styled website with a homepage, an “About” page, and some posts. Next, you’ll deploy it to production through GitHub and DigitalOcean’s App Platform.
Before you can deploy your code to DigitalOcean’s App Platform, you need to get your site in a Git repository, and push that repository to GitHub. The first thing to do is initialize a Git repo in your project directory:
- git init
Next, create a .gitignore
file at the project root so that you can exclude the contents of the node_modules
and _site
directories from being tracked in the Git repo.
node_modules
_site
After saving the file, run the commands below to add all the project files to the staging area, then make your initial commit:
- git add -A
- git commit -m "Initial version of the site"
The output should look similar to this:
Output[master (root-commit) e4e2063] Initial version of the site
15 files changed, 6914 insertions(+)
create mode 100644 .eleventy.js
create mode 100644 .gitignore
create mode 100644 _data/site.json
create mode 100644 _includes/layouts/base.njk
create mode 100644 _includes/layouts/page.njk
create mode 100644 _includes/layouts/post.njk
create mode 100644 _includes/nav.njk
create mode 100644 about/index.md
create mode 100644 css/style.css
create mode 100644 index.njk
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 posts/first-post.md
create mode 100644 posts/second-post.md
create mode 100644 posts/third-post.md
Navigate to GitHub, log in with your profile, and create a new empty repository for your project called eleventy-blog
(it can be public or private). Once the GitHub repo is created, copy the link to the repo, and add it as a remote location for your project in the terminal:
- git remote add origin https://github.com/username/eleventy-blog.git
Before you push your changes to the remote location, rename the default branch to main
to match what GitHub expects:
- git branch -M main
Finally, run the command below to push the main
branch to GitHub. Enter your GitHub account credentials when prompted.
Note: If two-factor authentication is enabled for your GitHub account, you’ll need to use a personal access token or SSH key when accessing GitHub on the command line. For more information, see Using two-factor authentication with the command line.
- git push origin main
The output should look similar to this:
OutputEnumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 4 threads
Compressing objects: 100% (19/19), done.
Writing objects: 100% (23/23), 64.41 KiB | 2.38 MiB/s, done.
Total 23 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), done.
To https://github.com/username/eleventy-blog.git
* [new branch] main -> main
In this step, you added your site to a Git repository, which you then pushed to GitHub. You are now ready to deploy your website to DigitalOcean’s App Platform.
Go ahead and log in to your DigitalOcean account, then head over to https://cloud.digitalocean.com/apps
and click the green Create button on the top right. Choose the Apps option in the dropdown, then, on the resulting page, select GitHub as your source.
You’ll be redirected to GitHub and prompted to give DigitalOcean access to your repositories. You can choose all repositories, or just the ones you wish to deploy. Click Install and Authorize once you’ve made your choice. You should be redirected back to the Choose Source page once again.
On the Choose Source page, select GitHub, then choose the eleventy-blog
repository from the Repository dropdown. Ensure that the selected branch is main
, and the checkbox to autodeploy code changes is ticked, then click Next to continue.
On the next page, your project will be auto detected as a Node.js project. You may need to change its Type to Static Site, and the Output Directory to _site
as shown in the screenshot below. Click Next to continue once everything matches.
Name your static site, and click Next to go to the Finalize and Launch screen.
Static sites are free, so retain the default Starter selection under Plans, and press Launch Starter App at the bottom of the page.
Your site will build immediately, and you should see a success message after a few minutes.
By default, your app will be given a sub-domain on ondigitalocean.app
. Now that your site is deployed, you can visit the provided URL to view the live website in your browser. You can also register a custom domain for your site by following How to Manage Domains in App Platform.
In this tutorial, you built a static site with Eleventy and deployed to DigitalOcean’s App Platform. Moving forward, you can make changes to your website locally, push the changes to GitHub, and DigitalOcean will automatically update the live site. You can go to the app dashboard to see the progress of the build at any time.
To build on what you’ve achieved in this tutorial, visit the Eleventy docs to learn more about how you can customize your site’s templates, and add features like responsive images, syntax highlighting, and caching. If you’d like to get started quickly without creating your own theme from scratch, check out the starter projects page.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.