Eleventy and the Static Site Landscape
Eleventy (also known as 11ty) has been gaining traction among static site generators thanks to its zero-config setup, purely static output, and reputation for easily achieving perfect Lighthouse scores. To understand its appeal, it helps to first clarify what a static site is. Unlike a dynamic site that assembles pages by querying a database at runtime (as with WordPress) or pulling content from APIs client-side (as with non-SSR JavaScript frameworks), a static site's HTML, CSS, and assets are all compiled before being pushed to a host.
A static site generator is a build processor that compiles your content into static HTML, offering helpers like Markdown support and templating to avoid hand-writing repeated HTML. This lets you break pages into components and then assemble everything during a build process that can even run on your web host. Eleventy is one option among many—others include Jekyll, Hugo, Gatsby, Next, and Nuxt, with a comprehensive list maintained by Jamstack.org.
What Sets Eleventy Apart
Eleventy's main advantage is speed, both during builds and in the browser, since it doesn't require loading a client-side JavaScript bundle to serve content. Unlike Gatsby, there's no client-side hydration concern—the filesystem page creation defaults to static HTML. What truly distinguishes Eleventy is its support for intermixing up to ten different templating languages:
These languages can be combined within a single file or across layouts. For instance, you might write content in Markdown that feeds into a Nunjucks layout, or even loop over data with Nunjucks directly inside a Markdown file. This flexibility lets you design a workflow that suits your project.
Eleventy's --serve flag uses BrowserSync for local serving with hot-reload, making it a potential upgrade from build tools like Gulp. While zero-config means your site files can live in the project root, you can adjust input and output directories by creating a config file named .eleventy.js:
module.exports = function (eleventyConfig) {
return {
dir: {
// default: [site root]
input: "src",
// default: _site
output: "public",
},
};
};
By default, Eleventy uses filesystem page creation, which is ideal for quick starts. You can override this behavior by setting a custom permalink per file, for an entire directory, or dynamically based on data. Permalinks also enable more advanced uses we'll explore later.
During the build, Eleventy allows you to use JavaScript for filters and shortcodes to prepare content and data. Notably, this all happens without adding a client-side JavaScript bundle, even though JavaScript can be used as a templating language. However, you can successfully use Eleventy with little to no JavaScript knowledge.
Unlike Gatsby or WordPress, most Eleventy sites don't require plugins. Some plugins are available, but they aren't necessary for core functionality. You can build with plain HTML, and Eleventy only becomes as complex as your project demands.
Core Concepts to Know
Layouts and Templates
While these terms are often used interchangeably, Eleventy gives them specific meanings:
- Template: The generic term for all content files.
- Layout: A special template that wraps other content.
For example, all your Markdown files are templates, while a layout might be a Nunjucks file containing the HTML5 boilerplate with a slot for your template content.
Filters and Shortcodes
Filters and shortcodes modify content and create reusable template parts, available across Nunjucks, Liquid, Handlebars, and JavaScript templating. They're defined in .eleventy.js. Filters transform content in a way specific to its type—uppercasing strings or picking a random item from an array, for instance. Eleventy provides some built-in filters. Shortcodes, meanwhile, allow creating reusable parts that accept arguments and can be standalone or paired (wrapping content with start and end tags).
A simple shortcode example renders the current year for a copyright notice:
eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`);
In Nunjucks or Liquid templates, you'd use it as {% year %}. More examples, including paired shortcodes, are in the Eleventy documentation.
Collections
Collections group related content, typically by defining tags in frontmatter. Tag syntax supports single strings, arrays like ["tagA", "tagB"], or YAML-style lists. For example, adding this frontmatter creates a "pages" collection:
---
tags: pages
---
Defined collections are accessible via the global collections object—our "pages" collection would be collections.pages. This returns an array you can loop over to generate links or teaser cards. You can also suppress normal file output and use collections solely for data display, useful for single-page site content.
Custom Data
Beyond file-based content, Eleventy supports custom data from JavaScript module exports or JSON files in the _data directory. This data can be a basic JSON array, the result of a fetch operation, or content retrieved from a headless CMS. All data in _data is exposed under a variable matching the filename—such as posts for posts.json. In Nunjucks, looping over that data looks like:
{% for post in posts %}
{{ post.title }}
{% endfor %}
Pagination and Generating Pages from Data
In Eleventy, "pagination" refers to iterating over a data set and defining a template for outputting that data in chunks. You create a dedicated file that defines pagination in frontmatter, setting up the intended output for each chunk. This file serves as its own template—you can define a layout and add tags to create a collection for flexible output.
Important: If you're pulling content from a CMS via custom data, pagination is the Eleventy method for dynamically turning data into pages.
Here's an example referencing posts custom data we'll assume is fetched from a headless CMS. The size of 1 means each pagination chunk produces one page. The alias creates a reference to the current item, used in the permalink definition and the template body. This file could be src/generate/posts.njk:
---
pagination:
data: posts
size: 1
alias: post
addAllPagesToCollections: true
permalink: "/{{ post.title | slug }}/"
tags: posts
layout: post
templateEngineOverride: njk, md
---
{{ post.body | safe }}
Here, the permalink outputs pages at the site root. You could add a prefix like /posts/{{ post.title | slug }}. To include all generated pages in a collection (not just the first), set addAllPagesToCollections to true.
If your content arrives as Markdown rather than pre-compiled HTML, use templateEngineOverride. In the snippet above, it's set to njk, md, meaning the content is first processed as Nunjucks (to resolve variables) then as Markdown (to compile the output). As for the safe keyword used here, that's next.
Setting Up A First Project
Eleventy is distributed as a scoped package, so installation follows the npm convention for such packages:
npm install @11ty/eleventy
A practical convenience is to define npm scripts in package.json so common commands are shorter:
"scripts": {
"start": "eleventy --serve",
"build": "eleventy"
}
The --serve flag starts a local development server through BrowserSync.
Before creating content, it helps to configure Eleventy’s input and output directories (commonly src and _site) in .eleventy.js. Inside the input directory, the _includes folder is one of the few expected directories. A common practice is to place an HTML5 boilerplate there as a layout file, often named base and written in Nunjucks:
<!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>
Nunjucks uses double curly braces for variables. The title variable will be supplied later via front matter, while the content variable is Eleventy’s built-in slot for all page content. That variable must be paired with the safe filter so compiled HTML is rendered rather than escaped.
With the layout in place, the site’s home page can be created as index.md, adding a title and layout in its front matter:
---
title: Hello Smashing Readers!
layout: base.njk
---
Thanks for reading — hope you’re excited to try Eleventy!
Running the project with npm start triggers BrowserSync, which serves the site at localhost:8080 when available. The browser must be opened manually unless extra configuration is added.
Adding A Stylesheet
CSS is not among the file types Eleventy processes automatically. To include it, create a stylesheet in the input directory (for example, css/style.css) and extend the Eleventy config in .eleventy.js:
module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("./src/css/");
eleventyConfig.addWatchTarget("./src/css/");
// - input/output customization if using -
};
The config first adds the css directory as a passthrough copy so Eleventy pushes it unchanged to the output directory. It is also registered as a watch target so style edits during npm start trigger a rebuild.
The stylesheet link then goes into the base layout, with the path passed through Eleventy’s url filter to adjust the relative file path at build time:
<link rel="stylesheet" href="{{ '/css/style.css' | url }}" />
Creating A Second Content Type
To exercise layouts and collections, add a pages directory with one or two Markdown files containing a title in front matter—but no layout. Instead of repeating the layout assignment, a data directory file applies data to every file in its folder. The file must match the directory name, so create pages.json:
{
"layout": "page.njk",
"tags": "pages"
}
This JSON defines the layout for files in pages and also adds a tag to group them into the “pages” collection. Since that layout does not exist yet, create _includes/page.njk:
---
layout: base.njk
---
<article>
{{ content | safe }}
</article>
This demonstrates layout chaining: the page layout reuses base while adding an <article> wrapper. All content in pages therefore benefits from both templates, reducing duplication of boilerplate and overall site structure.
The collection can now be output on the home page by looping over it with Nunjucks and rendering each item as a Markdown link:
{% for post in collections.post -%}
- [{{ post.data.title }}]({{ post.url }})
{% endfor %}
One caveat: the Markdown renderer defaults to the Liquid templating engine. If a template uses Nunjucks features beyond basic loops, Eleventy must be told how to process the file. Adding templateEngineOverride: njk, md to the front matter fixes this by declaring that the file should be processed as both Nunjucks and Markdown.
Starters As An Alternative
Eleventy does not use the “theme” concept from WordPress and similar systems; instead it relies on “starters.” Many are listed in the official Eleventy documentation. Popular setups include Sass-based starters with added build steps, as well as minimal starters that mirror the structure shown here and add examples of external data fetching and navigation partials based on collections.
Beyond The Basics
Generating Non-HTML Output
Permalinks have an additional capability beyond fixing URL structures: they can change the file type that Eleventy outputs. This is useful for generating RSS feeds and sitemaps, both typically XML. Since template languages still apply, collections can be looped over with Nunjucks to keep such files current.
Custom Collections
When tags are not flexible enough, collections can be created or altered from the .eleventy.js config. The addCollection function filters an existing collection, for example based on a custom front matter key accessed from each content item’s data object:
eleventyConfig.addCollection("specialCollection", function (collection) {
return collection.getAll().filter((post) => post.data.customKey);
});
Further approaches to modifying and using collections are covered in the Eleventy documentation.
The Data Cascade
The data cascade determines how template data is compiled, including front matter, data files, and global data. Understanding this mechanism is essential for more advanced Eleventy projects; official docs and community guides provide detailed walkthroughs.
Useful Plugins
Eleventy has a plugin ecosystem but not every project needs one. A few official and community plugins are commonly used:
- @11ty/eleventy-plugin-rss provides filters for creating RSS feeds and includes a sample feed implementation.
- @11ty/eleventy-plugin-syntaxhighlight moves Prism-based code highlighting into the build process, adding the appropriate classes to code blocks ahead of time so only a Prism CSS theme is needed.
- @11tyrocks/eleventy-plugin-social-images generates social share images via Puppeteer, with prebuilt templates and configurable custom templates.
The rest of the official plugins handle navigation, image processing, and other recurring requirements.
Assessing Eleventy For Your Needs
Eleventy is a sensible default when content does not need to be served dynamically. That does not mean every page must be pure static output: client-side JavaScript can still handle API calls or interactive widgets, and services like IFTTT or Zapier can trigger rebuilds through host webhooks for scheduled refreshes. External content from headless CMSs and other APIs integrates through custom data and pagination.
A major strength is the lack of strong opinions about site architecture. Only a few directories like _includes and _data are expected, and even their naming can be customized. That flexibility helps when migrating an existing file structure. Conversely, projects wanting a stricter scaffold might prefer a more opinionated tool.
Multiple template languages, filters, shortcodes, and layouts let Eleventy adapt to each project. Starters accelerate setup so content creation can start immediately, and the static output is inherently fast. For more complex build pipelines, familiar tools such as Webpack, Gulp, or Parcel can be integrated—sometimes already included in a starter—while Node scripts remain available throughout the build. Eleventy has proven capable on large projects, powering sites like Google’s web.dev and Netlify’s marketing site.
Where to Go From Here
Eleventy has a small but active community, and the ecosystem is growing steadily. If you get stuck, the official @eleven_ty Twitter account is a reliable place to ask questions; creator Zach Leatherman is known for responding quickly or retweeting queries to help you find an answer. The author has also published more than 20 Eleventy projects since early 2020 — starters, plugins, side projects, and course material — many of which are collected on 11ty.Rocks, alongside tutorials and tips.
A few high-quality resources stand out for deepening your understanding of the tool:
- Andy Bell’s paid course, "Learn Eleventy From Scratch", offers comprehensive, structured instruction.
- Tatiana Mac’s tutorial series, beginning with "Beginners Guide to Eleventy", assumes no prior experience with static site generators and explains each step thoroughly.
- Bryan Robinson’s free YouTube course walks through converting a free HTML theme into an Eleventy site.
The points covered in this guide reflect some of the trickiest details to uncover when starting out with Eleventy. Whether you are building a simple blog or a complex multi-template project, the mental model established here should carry you through your first builds and beyond.




