Eleventy is a Static Site Generator created and maintained by Zach Leatherman. Eleventy allows you to develop websites based on templates and data or content files (YAML / Markdown / HTML / JSON / JS) in a source directory. Based on those source files, Eleventy will generate a fully functional static site in a destination folder. You will then be able to deploy that site on any web server capable of serving static files.
The stated goal of Eleventy is to be an alternative to Jekyll, written in Node rather than in Ruby. Like Jekyll, 11ty is a very approachable SSG, once the basic principles are well understood.
Node being quite fast, Eleventy is a performant SSG. It is also very flexible. Since it is written in Node, 11ty allows you to use the NPM ecosystem to extend its functionalities. You can also pick your favorite in a long list of templating languages. In this course we will use Nunjucks by Mozilla for all code samples.
Let's start by creating a folder for our new project:
mkdir eleventy-portfolio
cd eleventy-portfolio
You will need Node installed on your machine. You can then install Eleventy as an NPM module. The best way to proceed is to create a package.json
that will manage all our Node dependencies.
npm init -y
That will skip a bunch of questions and create a standard file. You can come back to it and edit your package.json
as you see fit. Here is the official documentation to help you find out about the available parameters and options.
We can then install Eleventy as a local dependency for our project:
npm install --save-dev @11ty/eleventy
At this point, Eleventy will only create a node_modules
folder and install itself, nothing more. To see it in action, we will have to create at least one file. Let's create a basic index.html
file that Eleventy will copy in the default _site
destination folder.
Once we have created that file, we can learn some basic Eleventy commands:
npx @11ty/eleventy
: to run Eleventynpx @11ty/eleventy --serve
: to run a local web server that will reload the site in your browser when the site changesnpx @11ty/eleventy --help
: to explore the list of available commands and flags
Now if we type npx @11ty/eleventy
in our terminal, Eleventy will create a _site
folder and generate our trusty index.html
file into it. Pretty impressive, right?
Let's configure Eleventy to better suit our needs.
We will use ESM (import
and export
) throughout this workshop. In order for all your .js
files to be interpreted as ESM instead of CommonJS, add "type": "module"
to your package.json
file.
file: package.json
{
"type": "module"
}
We will make a basic project architecture and configure Eleventy by creating an eleventy.config.js
configuration file at the root of our project.
- Remove Eleventy's default destination
./_site
folder. - Create a
./src
folder and moveindex.html
into it. - Create an
eleventy.config.js
file at the root of the project.
Let's start by specifying source and destination directories for Eleventy:
file: eleventy.config.js
// override default config
export const config = {
dir: {
input: "src",
output: "dist",
},
};
Now when we run the npx @11ty/eleventy
command in our terminal from the root of our project, Eleventy will generate a ./dist
folder and will copy our index.html
file to it.
We also can use this configuration file to tell Eleventy to copy any file or folder from the source directory to the destination directory. In order to instruct Eleventy to do so, we are going to use addPassthroughCopy
. Good candidates to copy are static assets like images and font files.
- Create a
./src/assets/img/
directory and drop a few optimized images in there. - Create a
./src/assets/fonts/
directory and drop a few font files in there. - Create a
./src/assets/js/
directory and drop a JavaScript file (or two) in there. - Create a
./src/assets/css/
directory and drop a CSS file in there.
Let's modify our eleventy.config.js
file as follows:
file: eleventy.config.js
export default function (eleventyConfig) {
// copy files
eleventyConfig.addPassthroughCopy("./src/assets/");
}
// override default config
export const config = {
dir: {
input: "src/",
output: "dist/",
data: "_data",
includes: "_includes",
},
templateFormats: ["njk", "md", "html"],
htmlTemplateEngine: "njk",
markdownTemplateEngine: "njk",
};
Eleventy will now copy the ./src/assets/
directory and everything it contains to the output directory, while preserving the directories structure.
By default, Eleventy will ignore the node_modules
directory as well as the folders, files and globs specified in your .gitignore
file.
We can also create a .eleventyignore
file at the root of our project and specify a file, directory or glob pattern per line to explicitly tell Eleventy to ignore all matching files or directories.
Alternatively, you can ignore files using the configuration API in your eleventy.config.js
file. That's my preferred method. While we are at it, we will also exclude those files from Eleventy's watch list.
file: eleventy.config.js
export default function (eleventyConfig) {
// avoid processing and watching files
eleventyConfig.ignores.add("./src/assets/**/*");
eleventyConfig.watchIgnores.add("./src/assets/**/*");
// copy files / folders
eleventyConfig.addPassthroughCopy("./src/assets/");
}
// override default config
export const config = {
dir: {
input: "src/",
output: "dist/",
data: "_data",
includes: "_includes",
},
templateFormats: ["njk", "md", "html"],
htmlTemplateEngine: "njk",
markdownTemplateEngine: "njk",
};
Eleventy doesn't offer an asset pipeline or a build tool by default. I always almost use Eleventy with build tools, whether with NPM scripts, Vite or any other alternatives.
When you start using build tools to create an assets pipeline, you will likely have to modify your addPassthroughCopy
and ignore assets directories that will not need to be handled by Eleventy. Your build step will take care of them and generate what you need in your ./dist
directory.
If, for example, you are using build tools to handle your CSS and JavaScript files, you will have to make the following modifications:
file: eleventy.config.js
export default function (eleventyConfig) {
// avoid processing and watching files
eleventyConfig.ignores.add("./src/assets/**/*");
eleventyConfig.watchIgnores.add("src/assets/**/*");
// copy files
eleventyConfig.addPassthroughCopy("./src/assets/fonts/");
eleventyConfig.addPassthroughCopy("./src/assets/img/");
// watch compiled CSS and JS for change and reload browser
eleventyConfig.setServerOptions({
watch: ["./dist/assets/css/**/*.css", "./dist/assets/js/**/*.js"],
});
}
// override default config
export const config = {
dir: {
input: "src/",
output: "dist/",
data: "_data",
includes: "_includes",
},
templateFormats: ["njk", "md", "html"],
htmlTemplateEngine: "njk",
markdownTemplateEngine: "njk",
};
Eleventy will now completely ignore the ./src/assets/css/
and ./src/assets/js/
directories, while your build tools and scripts will generate the required outputs in your ./dist/
directory. The Eleventy Dev Server will also reload your browser whenever your compiled CSS and JS files change in your ./dist/assets/js/
and ./dist/assets/css/
directories.
Personally, I use NPM scripts combined with esbuild (JS files) Lightning CSS (CSS files) for most of my Eleventy projects.
Eleventy allows you to work with two main data sources:
- Markdown files (for the main content) and YAML front matter (for the rest of the data structure) that can easily be turned into collections (more on that later).
- JSON and/or JS data files that can either be static or dynamic (fetched from an API).
These two types of data sources are not mutually exclusive. They are often used simultaneously in projects. Let's dive in.
Collections in Eleventy allow you to group content items in interesting ways and to use them in your templates.
Markdown files coupled with a YAML front matter allow you to use text files as structured data sources. It's a common feature of most SSG out there.
The Markdown part of the file generally represents the main content of your data and is usually converted to HTML. The YAML front matter allows you to create a data structure with different types of data (strings, arrays, objects, etc.).
If you want to build a blog, your blogposts are going to be represented by Markdown files with a YAML front matter that could look something like the following:
file: ./src/blog/2019-07-22-markdown-yaml-front-matter.md
---
title: "This is the title"
intro: "This is an introductory paragraph for a blogpost"
imageSmall: "testimage_600.jpg"
imageMedium: "testimage_1024.jpg"
imageBig: "testimage_1500.jpg"
imageAlt: "Alternative text for picture"
categories:
- front-end
- Jamstack
- Eleventy
---
## Level 2 title
This is some content for this blogpost.
In this case, the date of the blogpost will be derived from the filename. Another option is to specify a date in the YAML front-matter
---
date: 2024-11-26
---
Let's say you also need to model a data structure for a team. Each team member could be represented by a file like this one:
file: ./src/projects/jerome-coupe.md
---
name: "Jérôme"
surname: "Coupé"
image: "jerome_coupe.jpg"
mastodon: "https://mastodon.social/@jeromecoupe"
github: "https://github.com/jeromecoupe"
website: "https://www.webstoemp.com"
---
Jérôme Coupé is a looney front-end designer and teacher from Brussels, Belgium. When not designing or coding, he might have been seen downtown, drinking a few craft beers with friends.
For Eleventy to group those files in an array that will allow you to work with it in your templates, you have to create a collection. Any content item can be part of one or more collections.
To create a collection, you can assign the same tag
to various content items. Personally, I would much rather use the collection API and our trusty eleventy.config.js
file.
This API offers you different methods to declare your collections that each have their use. My favourite and most used one by far is getFilteredByGlob(glob)
that allows you create a collection from all files matching a defined glob pattern.
If all your Markdown files for your blogposts are in a ./src/blog/
directory, grouping them into a collection is quite easy. You have to add the following code to your eleventy.config.js
configuration file. While we are at it, we will also add our team
collection.
export default function (eleventyConfig) {
// blogposts collection
eleventyConfig.addCollection("blogposts", function (collection) {
return collection.getFilteredByGlob("./src/blog/*.md");
});
// team collection
eleventyConfig.addCollection("team", function (collection) {
return collection.getFilteredByGlob("./src/team/*.md");
});
// ... more configuration ...
}
You can now access your collections using collections.blogposts
and collections.team
in your templates. We will come back to that when we get to templating.
It is also important to know that, by default, Eleventy creates a collection that contains all your content items. This special collection can be accessed in your templates by using collections.all
.
When a collection is created, the following keys are automatically created:
page
: contains most of the variables created by Eleventypage.url
: url used to link to this piece of content. In general, this is based on thepermalink
key defined for your content items.page.fileSlug
: input filename minus template file extensionpage.filePathStem
: inputPath minus template file extensionpage.date
: date of the source filepage.inputPath
: path to the original source file for the template (includes input directory)page.outputPath
: path to the output file (depends on your output directory)page.outputFileExtension
: output file extensionpage.templateSyntax
: Comma separated list of template syntaxes used to process the filepage.rawInput
: raw unparsed/unrendered plaintext content for the current templatepage.lang
: available since 2.0 with the i18n plugin
data
: all data for this piece of content. includes data inherited from layouts and YAML front-matter data.content
ortemplateContent
: rendered content of this template. Does not include layout wrappers.
When you create a collection in Eleventy, its items are automatically sorted in ascending order using:
- The date specified in the filename or in the YAML front matter of the source file. The creation date on the filesystem is used as a fallback.
- If some files have an identical date, the full path of the file (including the filename) will be taken into account as well.
If what you need is to sort your collection items by date, you are covered. You can also use the reverse
filter in Nunjucks if needed.
By contrast, if you need to sort your team members alphabetically using the value of their surname
key, you will need to use the JavaScript sort
method.
export default function (eleventyConfig) {
// Team collection
eleventyConfig.addCollection("team", function (collection) {
return collection
.getFilteredByGlob("./src/team/*.md")
.sort((a, b) => a.data.surname.localeCompare(b.data.surname));
});
// ... more configuration ...
}
If you need to filter a collection to exclude some data, you can use the JavaScript filter
method. You can for example only include the blogposts that do not have a draft
key set to true
in their front matter and that have a publication date less recent than site generation date. Let's sort them by most recent dates
const now = new Date();
export default function (eleventyConfig) {
// blogposts collection
eleventyConfig.addCollection("blogposts", function (collection) {
return collection
.getFilteredByGlob("./src/blog/*.md")
.filter((item) => item.data.draft !== true && item.date <= now)
.reverse();
});
// ... more configuration ...
}
Aside from collections, the other data source you can use with Eleventy are data files. Those can be static or dynamic (fetched from an API).
By default, these files have to be stored in the ./src/_data/
directory. This can be modified using the eleventy.config.js
configuration file.
export default function (eleventyConfig) {
// ... more configuration ...
}
// override default config
export const config = {
dir: {
// more configuration
data: "_data",
// more configuration
},
// more configuration
};
Static data files are JSON or JS files containing key/value pairs.
file: ./src/_data/site.js
export default {
title: "Title of the site",
description: "Description of the site",
url: "https://www.mydomain.com",
baseUrl: "/",
author: "Name Surname",
buildTime: new Date(),
};
Those values can be accessed in your templates by using the filename as a key. Data contained in a ./src/_data/site.js
file will thus be accessible in your templates using the site
variable. To display any value in that file in yout template, you'll have to use dot notation: {{ site.author }}
.
Because data files can be JavaScript files, nothing is preventing you from connecting to an API in one of those files by using fetch
or axios
for example.
Each time you generate your site, Eleventy will execute that script and treat the JSON file returned by the API like a static one.
The Eleventy Fetch plugin allows you to simplfy your API calls et to locally cache responses during a configurable time period.
By default, Eleventy will use the folders and files structure in your source directory to generate static files in your output folder.
./src/index.html
will generate./dist/index.html
with/
as the URL../src/test.html
will generate./dist/test/index.html
with/test/
as the URL../src/subdir/index.html
will generate./dist/subdir/index.html
with/subdir/
as the URL.
This default behavior can be changed by using a static or dynamic permalink
variable in your content files or in your templates.
For example, to create a blog, you will need an index page at the following URL /blog/index.html
. Your Nunjucks template ./src/pages/blog.njk
should have that as the value of the permalink
key in its YAML front matter.
---
permalink: "/blog/index.html"
---
You can also use variables to create dynamic permalink
values. This could be in the YAML front matter of all your Markdown blogposts files.
---
permalink: "/blog/{{ page.fileSlug }}/index.html"
---
If you have a collection to group all your team members but you do not need a detail page for each member, you can use false
as the value of the permalink
key. Eleventy will not generate detail pages. In most of those cases, you won't need a dedicated layout
either.
---
permalink: false
layout: false
---
We will see later that you will then need to use the content
or templateContent
key to display the content of these Markdown files.
Instead of having to specify the same YAML front matter key / value pair for a bunch of files, Eleventy allows you to specify the same key/value pair for all the files in a directory by using JS or JSON directory data files.
If you want to specify the same key/value pair for permalink
and layout
for all of your blogposts, you can add a ./src/blog/blog.json
, ./src/blog/blog.11data.json
or ./src/blog/blog.11data.js
directory data file in the ./src/blog
directory and specify them there. Eleventy will apply those values to all the files in that directory or in its subdirectories.
file: ./src/blog/blog.json
{
"layout": "layouts/blogpost.njk",
"permalink": "blog/{{ page.fileSlug }}/index.html"
}
file: ./src/blog/blog.11tydata.js
export default = {
layout: "layouts/blogpost.njk",
permalink: "blog/{{ page.fileSlug }}/index.html",
};
Eleventy allows you to work with several templating languages. Nunjucks from Mozilla is powerful and easy to use, so that's generally my default choice. The documentation is quite well done so we don't need to cover everything. We will learn enough to create the templates we need for our small blog project, and you can expand your knowledge on your own later on.
Nunjucks has three main types of tags:
- comments:
{# this is a comment #}
- display:
{{ variable }}
- logic:
{% logic %}
This one is pretty easy: {# This is a comment #}
. Comments never appear in the rendered code.
This tags allow you to display variables like strings, numbers, booleans, arrays and objects in your templates. Most of the time, you will display variables created by you or by Eleventy when it runs. You can access properties using dot syntax, like in JavaScript. Those tags also allow you to perform maths operations or string concatenation.
Examples:
{{ "Hello World" }}
: displays the string "Hello World"{{ site.title }}
: displays the value of thetitle
key of thesite
object{{ site.title ~ " - is an awesome site" }}
will concatenate the value of the keytitle
of the site object with the string next to it{{ 8 + 2 }}
: displays10
Filters are essentially devoted to manipulate strings, numbers, booleans, arrays and objects while displaying them in your templates. Nunjucks makes a bunch of built-in filters available. Here are some examples.
{{ "this should be uppercase" | upper }}
will outputTHIS SHOULD BE UPPERCASE
.{{ [1,2,3,4,5] | reverse }}
will output5,4,3,2,1
. This filter is quite useful when combined with date sorted collections in Eleventy.{{ collections.blogposts | length }}
will display the number of items in yourblogposts
collection.
Eleventy allows you to create your own filters using JavaScript and the eleventy.config.js
configuration file. these filters can then be used in most templating languages you choose to use.
For example, Nunjucks does not have a built-in date formatting filter. We can create some in Eleventy using the Luxon
library. After installing that npm package in uour project, we can add a couple of filters to our config file.
// required packages
import { DateTime } from "luxon";
export default function (eleventyConfig) {
// ... more configuration ...
/**
* Format date: ISO
* @param {Date} date
*/
eleventyConfig.addFilter("dateIso", function (date) {
const jsDate = new Date(date);
const dt = DateTime.fromJSDate(jsDate);
return dt.toISO();
});
/**
* Format date: Human readable format
* @param {Date} date
*/
eleventyConfig.addFilter("dateFull", function (date) {
const jsDate = new Date(date);
const dt = DateTime.fromJSDate(jsDate);
return dt.setLocale(locale).toLocaleString(DateTime.DATE_FULL);
});
}
Once created, we can use these filters in our templates.
<p><time datetime="{{ page.date | dateIso }}">{{ page.date | dateFull }}</time></p>
These tags allow you to execute operations and can be used to create variables, for loops, control structures, etc.
The code below will assign all blogposts in the blogposts
collection to an allBlogposts
variable and use the built-in reverse
filter to sort them by reverse date order.
{% set blogposts = collections.blogposts | reverse %}
Nunjucks will allow you to use traditional control structures like if
and else
as well as comparison operators like ===
, !==
, >
or logical operators like and
, or
and not
.
{% if collections.blogposts | length %}
<p>There is at least one blogpost in this collection</p>
{% endif %}
{% if not collections.blogposts | length %}
<p>There is no blogpost in this collection</p>
{% endif %}
{% if collections.blogposts | length >= 2 %}
<p>There is at least two blogposts in this collection</p>
{% endif %}
When you have to display data, whether they come from an API or from Markdown files, you will have to walk through arrays or objects using for
loops. Let's display title and introductions for all our blogposts in an HTML list.
{% set blogposts = collections.blogposts %}
{% for entry in blogposts %}
{% if loop.first %}<ul>{% endif %}
<article>
<h2><a href="{{ entry.url }}">{{ entry.data.title }}</a></h2>
<p>{{ entry.data.intro }}</p>
</article>
{% if loop.last %}</ul>{% endif %}
{% else %}
<p>No blogposts found</p>
{% endfor %}
You will notice that we can use loop.first
and loop.last
to only spit out the <ul>
and </ul>
in the first and last iterations of the loop, respectively. In Nunjucks, an else
clause is executed when no data is returned, which allows us to display a warning in HTML.
On top of offering you an {% include %}
tag, Nunjucks uses template inheritance as its layout model with {% extends %}
. This allows you to define blocks with {% block blockname %}
in a template and then to override the content of those blocks with child templates that extends the parent one. These chains of templates can be as long as you wish.
Includes as well as template inheritance can be used with Eleventy. The only quirk is that templates to extend as well as template to include must all be located in the includes directory specified in your eleventy.config.js
configuration file. By default, this directory is _includes
and the path you specify in your config file is relative to your source directory.
file: eleventy.config.js
export default function (eleventyConfig) {
// more configuration
}
// override default config
export const config = {
dir: {
// path is relative to the input directory
// "_includes" is the default value
includes: "_includes",
},
// more configuration
};
When a parent template is extended by a child template, variables defined in the child template are accessible in the parent template.
Apart from {% extends %}
and {% include %}
, Nunjucks allows you to use macros that are reusable code snippets to which you can pass variables. Eleventy has a similar concept with the shortcodes.
Let's come back to our project and create the templates we need using everything we have learned so far.
file: ./src/_includes/layouts/base.njk
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{ metaTitle }} - {{ site.title }}</title>
<meta name="description" content="{{ metaDescription }}">
<meta name="author" content="{{ site.author }}">
<!-- CSS -->
<link rel="stylesheet" href="/css/main.min.css?ref={{ site.buildTime | date('X') }}">
<!-- Feed -->
<link rel="alternate" href="/feed.xml" title="{{ site.title }} - Blog RSS" type="application/atom+xml">
<!-- Open Graph -->
<meta property="og:type" content="article" />
<meta property="article:author" content="{{ site.author }}" />
<meta property="og:url" content="{{ site.url ~ page.url }}" />
<meta property="og:title" content="{{ metaTitle }}" />
<meta property="og:description" content="{{ metaDescription }}" />
<meta property="og:image" content="{{ metaImage }}" />
<!-- Favicon + Apple icon (minimal) -->
<link rel="icon" href="favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
</head>
<body>
{% include "partials/siteheader.njk" %}
{% block content %}{% endblock %}
{% include "partials/sitefooter.njk" %}
<script src="/js/main.bundle.js?ref={{ site.buildTime | date('X') }}"></script>
</body>
</html>
Here is an included file for the footer
file: ./src/_includes/partials/sitefooter.njk
<div class="c-sitefooter">
<p>© {{ site.buildTime | date("Y") }} - La casa productions</p>
</div>
As far as blogposts go, we need a special layout which will extend our base layout. This blogpost layout will be used by all the Markdown files in our collection to generate detail pages. This layout is the one specified for all our blogposts using a directory data file (see above).
file: ./src/_includes/layouts/blogpost.njk
{% extends "layouts/base.njk" %}
{% set metaTitle = title %}
{% set metaDescription = intro %}
{% set metaImage = site.url ~ "/assets/img/blogposts/" ~ imageSmall %}
{% block content %}
<main>
<div class="c-blogpost">
<div class="c-blogpost__media">
<picture>
<source
srcset="/assets/img/blogposts/{{ imageMedium }} 1024w,
/assets/img/blogposts/{{ imageBig }} 1500w"
sizes="(min-width: 1140px) 1140px,
100vw"
media="(min-width: 500px)">
<img
src="/assets/img/blogposts/{{ imageSmall }}"
class="o-fluidimage"
alt="{{ imageAlt }}">
</picture>
</div>
<div class="c-blogpost__body">
<header>
<p class="c-suptitle c-suptitle--dark"><time datetime="{{ page.date | date('YYYY-MM-DD') }}">{{ page.date | date("MMMM Do, YYYY") }}</time></p>
<h1 class="c-h1">{{ title }}</h1>
<div class="c-blogpost__intro">
<p>{{ excerpt }}</p>
</div>
</header>
<div class="c-richtext">
{{ content | safe }}
</div>
</div>
</div>
</main>
{% endblock %}
All blogposts in markdown use this blogposts.njk
which will, in turn, extend the base.njk
template to generate all blogposts detail pages.
The blogposts files specify the layout they use using a layout
key taking the path to the layout file as value (here layouts/blogpost.njk
). As we have seen earlier, it is often more practical to use directory data files to spécify that layout
key for all files in a directory.
Here is an example of template for the about page, where will will display a list of our team members. It extends our base layout and sets several variables that will be available in the base layout.
file: ./src/pages/about.njk
---
permalink: /about/index.html
---
{% extends "layouts/base.njk" %}
{% set metaTitle = "About us" %}
{% set metaDescription = "Meet the team behind the blog" %}
{% set metaImage = site.url ~ "/img/meta/team.jpg" %}
{% block content %}
<h1 class="h1">Meet our awesome team</h1>
{% set team = collections.team %}
{% for member in team %}
{% if loop.first %}<ul class="c-teamlist">{% endif %}
<li class="c-teamlist__item">
<article class="c-teammember">
<img class="o-fluidimage" src="/assets/img/team/{{ member.image }}" >
<h2 class="c-teammember__title">{{ member.name }} {{ member.surname }}</h2>
<div class="c-teammember__bio">{{ member.content | safe }}</div>
{% if member.mastodon or member.github or member.website %}
<ul class="u-hlist u-hlist--xs">
{% if member.mastodon %}<li><a href="{{ member.mastodon }}">{% include "svg/icon-mastodon.svg" %}</a></li>{% endif %}
{% if member.github %}<li><a href="{{ member.github }}">{% include "svg/icon-github.svg" %}</a></li>{% endif %}
{% if member.website %}<li><a href="{{ member.website }}">{% include "svg/icon-website.svg" %}</a></li>{% endif %}
</ul>
{% endif %}
</article>
</li>
{% else %}
<p>No team member found</p>
{% if loop.last %}</ul>{% endif %}
{% endfor %}
{% endblock %}
For the archive page of our blog, we will use the pagination provided by Eleventy. Pagination specifies what data
must be paginated (this can be any iterable), how many items must be displayed per page with size
and which alias
must be used for the paginated data.
file: ./src/pages/blog.njk
---
pagination:
data: collections.blogposts
size: 12
alias: posts
permalink: blog{% if pagination.pageNumber > 0 %}/page{{ pagination.pageNumber + 1}}{% endif %}/index.html
---
{% extends "layouts/base.njk" %}
{% set metaTitle = "Blog archives" %}
{% set metaDescription = "A blog about Belgium" %}
{% set metaImage = site.url ~ "/img/meta/default.jpg" %}
{% block content %}
<h1 class="h1">A blog about Belgium</h1>
{% for post in posts %}
{% if loop.first %}<ul class="l-grid l-grid--fluid">{% endif %}
<li>
<article class="c-blogpost">
<a class="c-blogpost__link" href="{{ post.url }}">
<img class="c-blogpost__image o-fluidimage" src="/assets/img/blogposts/{{ post.imageSmall }}" >
<h2 class="c-blogpost__title">{{ post.title }}</h2>
<p class="c-blogpost__intro">{{ post.intro }}</p>
</a>
</article>
</li>
{% if loop.last %}</ul>{% endif %}
{% else %}
<p>No blogpost found</p>
{% endfor %}
{# pagination #}
{% set totalPages = pagination.hrefs | length %}
{% set currentPage = pagination.pageNumber + 1 %}
{% if totalPages > 1 %}
<ul class="c-pagination">
{% if pagination.href.previous %}
<li class="c-pagination__item c-pagination__item--first">
<a class="c-pagination__link" href="{{ pagination.href.first }}">First</a>
</li>
{% endif %}
{% if currentPage > 1 %}
<li class="c-pagination__item">
<a class="c-pagination__link" href="{{ pagination.hrefs[pagination.pageNumber - 1] }}">{{ currentPage - 1 }}</a>
</li>
{% endif %}
<li class="c-pagination__item">
<span class="c-pagination__current" href="{{ pagination.hrefs[pagination.pageNumber] }}">{{ currentPage }}</span>
</li>
{% if currentPage < totalPages %}
<li class="c-pagination__item">
<a class="c-pagination__link" href="{{ pagination.hrefs[pagination.pageNumber + 1] }}">{{ currentPage + 1 }}</a>
</li>
{% endif %}
{% if pagination.href.next %}
<li class="c-pagination__item c-pagination__item--last">
<a class="c-pagination__link" href="{{ pagination.href.last }}">Last</a>
</li>
{% endif %}
</ul>
{% endif %}
{% endblock %}
The pagination function in Eleventy is much more powerful than it seems at first sight. If the data for our blogposts came as a big JSON file returned by an API, we could use the ./src/_data/_blogposts.js
file as a data source and use the same pagination function to generate all the detail pages with a single file. In order to accomplish that, we have to specify a value of 1
for size
and to craft a dynamic permalink pattern that corresponds to the URLs we want.
Here is a simplified template we could use:
file: ./src/pages/blogpost_entry.njk
---
pagination:
data: blogposts
size: 1
alias: blogpost
permalink: blog/{{ blogpost.slug }}/index.html
---
{% extends "layouts/base.njk" %}
{% set metaTitle = blogpost.title %}
{% set metaDescription = blogpost.intro %}
{% set metaImage = site.url ~ "/assets/img/blogposts/" ~ blogpost.imageSmall %}
{% block content %}
<h1>{{ blogpost.title }}</h1>
<p><time datetime="{{ blogpost.date | date('Y-M-DD') }}">{{ blogpost.date | date("MMMM Do, Y") }}</time></p>
<p>{{ blogpost.intro }}</p>
{{ blogpost.body | safe }}
{% endblock %}
Start with the provided static templates to create a fully functional blog.
- Configure Eleventy and create a date and a limit filter
- Generate a homepage with the list of the latest 6 blogposts using the
limit
filter - Generate a paginated archive for all our blogposts
- Generate detail pages for blogposts
- Create an about page listing team members and providing contact details
- Create a navigation using a data file (home, blog, about). The navigation must highlight the current section.