Automatic Page Hierarchy with the Eleventy Navigation Plugin

Automatic generation of page hierarchies based on the directory tree to enable breadcrumbs via the Eleventy Navigation plugin.

Contents

Introduction

The Eleventy Navigation Plugin enables the addition of breadcrumbs to a website. Hierarchical relationships between templates are established by adding the corresponding key and parent properties to the eleventyNavigation front matter object. Additionally, a title property can be supplied for the current entry, while the order property defines the sequence of the pages.

Building these relationships by manually setting the key and parent properties requires finding appropriate key names. However, if the website templates are stored in a 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 that use multiple root paths (such as /en/ and /de/), the solution requires a slight adjustment:

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.
    },
  },
};

Implementation Notes

This implementation uses the pages’ URLs as unique keys for the navigation entries. By default, the Eleventy Navigation plugin uses these keys as the entry titles. However, since the keys consist of raw URLs, the additional title property is also populated inside eleventyComputed.js. If a template manually defines a title, that value is used; otherwise, the page’s default title serves as a fallback. Similarly, the parent and key properties are only populated automatically if they have not already been defined in the template’s front matter.