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>© {{ 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.