Eleventy

Getting Started with Eleventy for Publishers

Eleventy (11ty) is a JavaScript-based static site generator that has grown into one of the most flexible options in its category. Where Hugo makes strong structural decisions and asks you to work within them, Eleventy makes almost none — it is a set of tools for turning content into HTML, with minimal opinions about how your project should be organized.

That flexibility is genuinely powerful and genuinely requires more upfront decision-making. This guide gets a new Eleventy site to a working, publishable state.

Installing Eleventy

Eleventy requires Node.js (version 18 or later). Verify your Node version:

node --version

Create a project directory and initialize it:

mkdir my-publication
cd my-publication
npm init -y
npm install @11ty/eleventy --save-dev

Eleventy installs as a project dependency rather than a global binary, which keeps different projects on different versions without conflicts.

Your First Template

Create a simple Markdown file at the project root:

echo "# Hello World" > index.md

Run Eleventy:

npx eleventy

Eleventy builds the project into _site/. Open _site/index.html — your Markdown has been converted to HTML. The build command is npx eleventy; the development server with live reload is npx eleventy --serve.

Project Structure

Eleventy has no required directory structure. A sensible convention for a publishing site:

my-publication/
├── src/
│   ├── _includes/       # Layout templates and partials
│   ├── _data/           # Global data files
│   ├── posts/           # Blog posts in Markdown
│   └── index.md         # Homepage
├── public/              # Static assets (CSS, images, fonts)
├── .eleventy.js         # Configuration
└── package.json

Configure Eleventy to use this structure in .eleventy.js:

module.exports = function(eleventyConfig) {
  // Copy static assets to _site
  eleventyConfig.addPassthroughCopy("public");

  return {
    dir: {
      input: "src",
      output: "_site",
      includes: "_includes",
      data: "_data"
    }
  };
};

Layouts

Create a base layout at src/_includes/base.njk (Nunjucks is the most commonly used templating language in Eleventy, though you can use any of the eleven supported options):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ title }} — My Publication</title>
  <link rel="stylesheet" href="/public/style.css">
</head>
<body>
  <header>
    <nav>
      <a href="/">My Publication</a>
    </nav>
  </header>
  <main>
    {{ content | safe }}
  </main>
  <footer>
    <p>&copy; {{ build.year }} My Publication</p>
  </footer>
</body>
</html>

Apply the layout in your Markdown frontmatter:

---
layout: base.njk
title: About This Publication
---

Content goes here.

Writing Posts

Create Markdown files in src/posts/:

---
layout: base.njk
title: "My First Post"
date: 2026-04-29
tags:
  - publishing
  - getting started
---

Post content in Markdown.

Collections

Eleventy’s collections system is how you group and access content. Posts tagged in frontmatter are automatically available in collections by tag name.

To access all posts tagged “publishing” in a template:

{% for post in collections.publishing %}
  <article>
    <h2><a href="{{ post.url }}">{{ post.data.title }}</a></h2>
    <time>{{ post.date | htmlDateString }}</time>
  </article>
{% endfor %}

For a “all posts” collection regardless of tag, use collections.all or create a named collection in .eleventy.js:

eleventyConfig.addCollection("posts", function(collection) {
  return collection
    .getFilteredByGlob("src/posts/*.md")
    .sort((a, b) => b.date - a.date);
});

Then in templates: collections.posts.

Filters

Eleventy’s filter system lets you transform data in templates. Add custom filters in .eleventy.js:

// Date formatting
eleventyConfig.addFilter("readableDate", dateObj => {
  return new Date(dateObj).toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric"
  });
});

// Limit array length
eleventyConfig.addFilter("limit", (array, limit) => {
  return array.slice(0, limit);
});

// Word count
eleventyConfig.addFilter("wordcount", content => {
  return content.split(/\s+/).length;
});

Usage in templates:

<time>{{ post.date | readableDate }}</time>
<p>{{ post.templateContent | wordcount }} words</p>

Global Data

Place JSON or JavaScript files in src/_data/ to make data available to all templates:

// src/_data/site.js
module.exports = {
  name: "My Publication",
  description: "A publishing site",
  url: "https://mypublication.com",
  author: "Jane Smith"
};

In templates: {{ site.name }}, {{ site.url }}.

Data files can also be async functions, enabling content fetched from APIs at build time:

// src/_data/featuredPosts.js
module.exports = async function() {
  const response = await fetch("https://api.example.com/featured");
  return response.json();
};

Pagination

Eleventy generates paginated archive pages from collections:

---
layout: base.njk
title: Posts Archive
pagination:
  data: collections.posts
  size: 10
  alias: posts
permalink: "/page/{% if pagination.pageNumber > 0 %}{{ pagination.pageNumber + 1 }}/{% endif %}"
---

{% for post in posts %}
  <article>
    <h2><a href="{{ post.url }}">{{ post.data.title }}</a></h2>
  </article>
{% endfor %}

<nav>
  {% if pagination.href.previous %}
    <a href="{{ pagination.href.previous }}">← Older</a>
  {% endif %}
  {% if pagination.href.next %}
    <a href="{{ pagination.href.next }}">Newer →</a>
  {% endif %}
</nav>

RSS Feed

Create src/feed.njk:

---
permalink: /feed.xml
eleventyExcludeFromCollections: true
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>{{ site.name }}</title>
  <link href="{{ site.url }}/feed.xml" rel="self"/>
  <updated>{{ collections.posts[0].date | dateToRfc3339 }}</updated>
  {% for post in collections.posts | limit(20) %}
  <entry>
    <title>{{ post.data.title }}</title>
    <link href="{{ site.url }}{{ post.url }}"/>
    <id>{{ site.url }}{{ post.url }}</id>
    <updated>{{ post.date | dateToRfc3339 }}</updated>
  </entry>
  {% endfor %}
</feed>

Deploying Eleventy

The build command is npx eleventy, producing the _site/ directory. Add to package.json:

"scripts": {
  "build": "npx eleventy",
  "start": "npx eleventy --serve"
}

On Netlify, set the build command to npm run build and the publish directory to _site. On Cloudflare Pages, the same settings apply.

Eleventy’s build times are slower than Hugo but faster than most other JavaScript-based generators. For sites up to a few thousand posts, build times remain well under a minute.