Eleventy (or 11ty) is a Node.js static site generator (SSG). SSGs do most rendering work at build time to create a set of static HTML, CSS, and JavaScript files. The resulting pages need not have server-side dependencies such as runtimes or databases.

This leads to several key benefits:

  • hosting is simple: you’re serving HTML files
  • systems are secure: there’s nothing to hack
  • performance can be great.

Eleventy has become increasingly popular and has attracted attention from big names in web development. It’s ideal for content sites and blogs, but has been adapted for online shops and reporting systems.

In most cases, you’ll be using Eleventy to generate HTML pages from Markdown documents which insert content into templates powered by engines such as Nunchucks. However, this tutorial also demonstrates how to use Eleventy as a complete build system for all assets. You don’t necessarily need a separate system such as npm scripts, webpack or Gulp.js, but you can still enjoy automated builds and live reloading.

Do You Need a JavaScript Framework?

A few SSGs adopt client-side JavaScript frameworks such as React or Vue.js. You can use a framework with Eleventy, but it’s not enforced.

In my view, a JavaScript framework is probably unnecessary unless you’re creating a complex app. And if you’re creating an app, an SSG is not the right tool! Gatsby fans may disagree, so please challenge/ridicule me on Twitter!

Show Me the Code

Eleventy claims to be simple, but it can be daunting when moving beyond the basics. This tutorial demonstrates how to build a simple site with pages and blog/article posts — a task often handled by WordPress.

The full code is available at https://github.com/craigbuckler/11ty-starter. You can download, install, and launch it on Windows, macOS, or Linux by entering the following commands in your terminal:

git clone https://github.com/craigbuckler/11ty-starter
cd 11ty-starter
npm i
npx eleventy --serve

Then navigate to the home page at http://localhost:8080 in your browser.

The steps below describe how to build the site from scratch.

Install Eleventy

Like any Node.js project, start by creating a directory and initializing a package.json file:

mkdir mysite
cd mysite
npm init

Then install Eleventy as a development dependency:

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

Note: this project installs modules as development dependencies since they need only run on a development machine. Some hosts with automated build processes may require you to use standard runtime dependencies.

Render Your First Page

Create a src directory where all source files will reside, then create an index.md file inside it. Add home page content such as:

‐‐‐
title: 11ty starter site
‐‐‐

This is a demonstration website using the [11ty static site generator](https://www.11ty.dev/). It shows pages, blog posts, lists, and tags.

The whole build process is managed through 11ty.

Content between the ‐‐‐ dash markers is known as front matter. It defines name-value metadata about the page which can be used to set parameters for Eleventy and templates. Only a title is set here, but you’ll add descriptions, dates, tags, and other data shortly.

An Eleventy configuration file named .eleventy.js must be created in your project’s root folder. This simple example code returns an object, which specifies the following:

  1. the source src directory for source files
  2. the build directory where website files will be created
// 11ty configuration
module.exports = config => {

  // 11ty defaults
  return {

    dir: {
      input: 'src',
      output: 'build'
    }

  };
};

To build the site and start a live-reloading server powered by Browsersync, enter the following:

npx eleventy --serve

Eleventy renders everything it finds in the src directory and outputs the resulting content to build:

$ npx eleventy --serve
Writing build/index.html from ./src/index.md.
Wrote 1 file in 0.12 seconds (v0.11.0)
Watching...
[Browsersync] Access URLs:
 ---------------------------------------
       Local: http://localhost:8080
    External: http://172.27.204.106:8080
 ---------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 ---------------------------------------
[Browsersync] Serving files from: build

In this case, a single build/index.html file is created can be accessed by loading the URL http://localhost:8080 in your browser.

first 11ty render

The HTML file created at build/index.html contains content rendered from the markdown file at src/index.md:

<p>This is a demonstration website using the <a href="https://www.11ty.dev/">11ty static site generator</a>. It shows pages, blog posts, lists, and tags.</p>
<p>The whole build process is managed through 11ty.</p>

The Eleventy server can be stopped with Ctrl | Cmd + C.

Note: it’s rarely necessary to stop Eleventy during site development, because new files are automatically rendered. However, the sections below add further configuration options, so restarting is required.

Creating Templates

Eleventy can use almost any JavaScript templating engine. Nunchucks is a good option since it’s comprehensive and used throughout the documentation at 11ty.dev.

Change the front matter in src/index.md to this:

‐‐‐
title: 11ty starter site
description: This is a demonstration website generated using the 11ty static site generator.
layout: page.njk
‐‐‐

This instructs Eleventy to use the page.njk Nunchucks template for layout. By default, Eleventy looks for templates in an _includes sub-directory in the source directory (src/). Any files located there are not rendered themselves but are used during the build process.

Create this new template at src/_includes/page.njk:

{% include "partials/htmlhead.njk" %}

<main>
{% block content %}

  <h1>{{ title }}</h1>

  {{ content | safe }}

{% endblock %}
</main>

{% include "partials/htmlfoot.njk" %}

The template places the title defined in the page’s front matter within an <h1> heading and replaces {{ content }} with HTML generated from the Markdown. (It uses the safe Nunchucks filter to output HTML without escaping quotes and angle brackets.)

The two {% include %} definitions reference files included within the template. Create an HTML header file at src/_includes/partials/htmlhead.njk, which also uses the page’s title and description:

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

Then create the HTML footer at src/_includes/partials/htmlfoot.njk:

</body>
</html>

Stop and restart Eleventy with npx eleventy --serve.

The rendered buildindex.html file now contains a fully formed HTML page:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>11ty starter site</title>
  <meta name="description" content="This is a demonstration website generated using the 11ty static site generator.">
</head>
<body>

  <h1>11ty starter site</h1>

  <p>This is a demonstration website using the <a href="https://www.11ty.dev/">11ty static site generator</a>. It shows pages, blog posts, lists, and tags.</p>
  <p>The whole build process is managed through 11ty.</p>

</body>
</html>

Note: when you view the source within a browser, you’ll also see a <script> has been added by BrowserSync after the <body> element. This is used to trigger live reloads and will not be present in the final build (see the “Build a Production Site” section below).

Create Further Pages

You can now create further content, such as an obligatory “About Us” section.

src/about/index.md:

‐‐‐
title: About us
description: What we do.
‐‐‐

Some information about us.

src/about/team.md:

‐‐‐
title: Our team
description: Information about us.
‐‐‐

Who are we and what we do.

src/about/privacy.md:

‐‐‐
title: Privacy policy
description: We keep your details private.
‐‐‐

Our privacy policy.

None of these files reference a template in their front matter. Eleventy lets you define defaults for all files in a directory by creating a <directory-name>.json file. In this case, it’s named src/about/about.json. It sets JSON values to use when they’re not explicitly defined in the page’s front matter:

{
  "layout": "page.njk"
}

Rerun npx eleventy --serve and examine the build folder to see how the site is starting to take shape:

  • index.html: the home page
  • about/index.html: about us page
  • about/team/index.html: team page
  • about/privacy/index.html: privacy policy page

You can therefore use a slug-like URL in your browser. For example, http://localhost:8080/about/team/ shows the team page index.html file.

Unfortunately, it’s impossible to navigate between pages! You need a menu …

Create Navigation Menus

Eleventy provides a standard navigation plugin, which is installed by entering the following:

npm i @11ty/eleventy-navigation --save-dev

Plugins must be referenced in your .eleventy.js configuration file before the final return statement:

// 11ty configuration
module.exports = config => {

  /* --- PLUGINS --- */

  // navigation
  config.addPlugin( require('@11ty/eleventy-navigation') );


  // 11ty defaults
  return {

    dir: {
      input: 'src',
      output: 'build'
    }

  };
};

eleventyNavigation: front-matter sections must be defined in every page you want in the menu. The section sets the following:

  1. A key for the page’s menu. This could be identical to the title but is often shorter.
  2. An optional parent, which references the parent page’s key.
  3. An optional order number; lower values appear first in the menu.

The home page front matter in src/index.md can be updated accordingly:

‐‐‐
title: 11ty starter site
description: This is a demonstration website generated using the 11ty static site generator.
layout: page.njk
eleventyNavigation:
  key: home
  order: 100
‐‐‐

The about page at src/about/index.md:

‐‐‐
title: About us
description: What we do.
eleventyNavigation:
  key: about
  order: 200
‐‐‐

The team page at src/about/team.md:

‐‐‐
title: Our team
description: Information about us.
eleventyNavigation:
  key: team
  parent: about
  order: 210
‐‐‐

The privacy policy page at src/about/privacy.md:

‐‐‐
title: Privacy policy
description: We keep your details private.
eleventyNavigation:
  key: privacy
  parent: about
  order: 220
‐‐‐

Note: using order values in multiples of 10 or higher allows pages to be inserted between others later without any manual renumbering.

A navigation menu can now be added to the page template at src/_includes/page.njk:

{% include "partials/htmlhead.njk" %}

<header>
  <nav>
    {{ collections.all | eleventyNavigation | eleventyNavigationToHtml | safe }}
  </nav>
</header>

<main>
...

This is some magic Eleventy plugin code which examines all pages and filters them with an eleventyNavigation() function to create a hierarchical list. That list render is rendered to HTML using an eleventyNavigationToHtml() function.

Restart npx eleventy --serve load any page to view the menu.

the unstyled menu

You can now navigate to any page defined within eleventyNavigation front matter.

Improve the Navigation

The navigation plugin returns a basic HTML list:

<ul>
  <li><a href="/">home</a></li>
  <li>
    <a href="/about/">about</a>
    <ul>
      <li><a href="/about/team/">team</a></li>
      <li><a href="/about/privacy/">privacy</a></li>
    </ul>
  </li>
</ul>

This will be adequate for most sites, but you can improve it. For example:

  • provide an option to show the menu to a specific level — such as the top level only in the header and all pages in the footer
  • highlight the active page while making it unclickable
  • set styling classes for active and open menu items.

One way to achieve this is by creating a reusable shortcode, which will be familiar to anyone who’s used WordPress. A shortcode, and any optional arguments, runs a function which returns an HTML string that’s placed in the template.

Stop your Eleventy server and update the src/_includes/page.njk template to use a {% navlist %} shortcode in the <header> and <footer> sections:

{% include "partials/htmlhead.njk" %}

<header>
  <nav>
    {% navlist collections.all | eleventyNavigation, page, 1 %}
  </nav>
</header>

<main>
{% block content %}

  <h1>{{ title }}</h1>

  {{ content | safe }}

{% endblock %}
</main>

<footer>
  <nav>
    {% navlist collections.all | eleventyNavigation, page, 2 %}
  </nav>
</footer>

{% include "partials/htmlfoot.njk" %}

The navlist shortcode is passed three parameters:

  1. Every page filtered through the eleventyNavigation() function, which returns a hierarchical list of page objects. Each page defines a children array of subpages.
  2. The current page.
  3. An optional level. A value of 1 returns the HTML for the top level only. 2 returns the top level and all immediate child pages.

The navlist shortcode must be registered using an .addShortcode() function in .eleventy.js before the return statement. It’s passed a shortcode name and the function to call:

  /* --- SHORTCODES --- */

  // page navigation
  config.addShortcode('navlist', require('./lib/shortcodes/navlist.js'));

You can now export a function in lib/shortcodes/navlist.js. The code below recursively examines all pages to generate the appropriate HTML (don’t worry if this is difficult to follow).

Note: the shortcode file has been created outside of the src folder since it’s not part of the site, but you could also define it in src/_includes.

// generates a page navigation list
const
  listType      = 'ul',
  elementActive = 'strong',
  classActive   = 'active',
  classOpen     = 'open';

// pass in collections.all | eleventyNavigation, (current) page, and maximum depth level
module.exports = (pageNav, page, maxLevel = 999) => {

  function navRecurse(entry, level = 1) {

    let childPages = '';

    if (level < maxLevel) {
      for (let child of entry.children) {
        childPages += navRecurse(child, level++);
      }
    }

    let
      active = (entry.url === page.url),
      classList = [];

    if ((active && childPages) || childPages.includes(`<${ elementActive }>`)) classList.push(classOpen);
    if (active) classList.push(classActive);

    return (
      '<li' +
      (classList.length ? ` class="${ classList.join(' ') }"` : '') +
      '>' +
      (active ? `<${ elementActive }>` : `<a href="${ entry.url }">`) +
      entry.title +
      (active ? `</${ elementActive }>` : '</a>') +
      (childPages ? `<${ listType }>${ childPages }</${ listType }>` : '') +
      '</li>'
    );

  }

  let nav = '';
  for (let entry of pageNav) {
    nav += navRecurse(entry);
  }

  return `<${ listType }>${ nav }</${ listType }>`;

};

Rerun npx eleventy --serve and navigate to the About page. The header <nav> HTML now contains the following:

<ul>
  <li><a href="/">home</a></li>
  <li class="active"><strong>about</strong></li>
</ul>

The footer <nav> HTML contains this:

<ul>
  <li><a href="/">home</a></li>
  <li class="open active">
    <strong>about</strong>
    <ul>
      <li><a href="/about/team/">team</a></li>
      <li><a href="/about/privacy/">privacy</a></li>
    </ul>
  </li>
</ul>

Continue reading
Getting Started with Eleventy
on SitePoint.

Source: Site Point

Share