Automatic Page Hierarchy with the Eleventy Navigation Plugin
The Eleventy Navigation Plugin can be used to add breadcrumbs to a website. Hierarchical relationships between templates are set up by adding according key and parent properties to the eleventyNavigation front matter object. Additionally, a title property can be supplied for the current entry. The order of the pages in the navigation can be defined using the order property.
Setting the key and parent properties to build the relationship between templates requires finding appropriate key names. In case the templates of a website are stored in an hierarchical directory structure, the eleventyNavigation object can be populated automatically.
Solution for Websites with One Root
The solution below assumes that the website has a single root /. The checks for Object.keys(data).length === 0 are necessary as the file eleventyComputed.js is evaluated twice by Eleventy and the data object is empty on the first pass through (see bug report Objects are initially perceived to be empty in eleventyComputed during the first pass through #2240).
The code below has to be placed in the file content/_data/eleventyComputed.js:
import path from 'node:path';
export default {
eleventyNavigation: {
key: (data) => {
if (Object.keys(data).length === 0) {
return;
}
if (data.eleventyNavigation.key) {
return data.eleventyNavigation.key;
}
if (data.page.url === '') {
return '/';
}
return data.page.url;
},
parent: (data) => {
if (Object.keys(data).length === 0) {
return;
}
if (data.eleventyNavigation.parent) {
return data.eleventyNavigation.parent;
}
// The parent of the root page is `undefined`.
if (data.page.url === '/') {
return undefined;
}
// Build parent from the template's directory name.
// `path.dirname` returns the directory name without a trailing slash,
// but if the resulting directory would be an empty string, it returns a
// slash.
const parent = path.dirname(data.page.url);
if (parent === '/') {
return '/';
}
return parent + '/';
},
title: (data) => {
if (Object.keys(data).length === 0) {
return;
}
if (data.eleventyNavigation.title) {
return data.eleventyNavigation.title;
}
return data.title; // Use page title as fallback.
},
},
};Solution for Websites with Multiple Roots
For websites such as this one that use more than one root (/en/, /de/, etc.), the solution needs to be adjusted slightly:
import path from 'node:path';
export default {
eleventyNavigation: {
key: (data) => {
if (Object.keys(data).length === 0) {
return;
}
if (data.eleventyNavigation.key) {
return data.eleventyNavigation.key;
}
return data.page.url;
},
parent: (data) => {
if (Object.keys(data).length === 0) {
return;
}
if (data.eleventyNavigation.parent) {
return data.eleventyNavigation.parent;
}
// Build parent from the template's directory name.
// `path.dirname` returns the directory name without a trailing slash,
// but if the resulting directory would be an empty string, it returns a
// slash.
const parent = path.dirname(data.page.url);
// In case the page's parent is `/`, the page is one of the language
// roots (`/en/`, `/de/`, etc.) that should be navigation roots without
// a parent.
if (parent === '/') {
return undefined;
}
return parent + '/';
},
title: (data) => {
if (Object.keys(data).length === 0) {
return;
}
if (data.eleventyNavigation.title) {
return data.eleventyNavigation.title;
}
return data.title; // Use page title as fallback.
},
},
};The implementation uses the pages’ URLs as unique keys for the navigation entries. By default, the Eleventy Navigation plugin uses the keys as titles for the navigation entries. However, as these consist of the URLs, the additional title property is also populated inside eleventyComputed.js. In case the title is supplied in the template, this title is used, otherwise, the page’s title is taken as a fallback value. The parent and key properties are only populated automatically if they are not yet set in a template’s front matter too.