Eleventy з коробки вміє групувати статті за тегами, але справжніх категорій, та ще й вкладених (як на цьому блозі: «Софт» → «Windows», «Linux», «Android»), у ньому немає. Колись я зробив їх досить «криворуким» способом: окремий файл для кожної категорії, окрема колекція, вручну прописаний список посилань. Тоді я попросив у читачів підказати, як зробити розумніше. З часом спосіб знайшовся сам, і він набагато простіший.

Нижче покажу, як зробити в Eleventy категорії з ієрархією, автоматичні сторінки категорій з посторінковою навігацією і посилання на категорію в кожній статті. Якщо ти тільки починаєш, спершу прочитай, як створити статичний сайт на Eleventy.

Ідея: категорія – це тека

Eleventy сам перетворює структуру тек на адреси сторінок, тому найлогічніше, щоб тека статті і була її категорією:

web/
  php-html-css/
    php-html-css.json
    my-first-post/index.md
  wordpress/
    wordpress.json
    theme-from-scratch/index.md
soft/
  linux/
    linux.json
    best-distro/index.md

Стаття web/wordpress/theme-from-scratch/index.md відкриватиметься за адресою /web/wordpress/theme-from-scratch/, і вже з адреси видно, що вона в категорії «WordPress», яка вкладена у «Веб».

Крок 1. Назви категорій в одному місці

Усі категорії з їхніми назвами опиши в одному глобальному файлі даних _data/categories.json. Ключ – шлях теки, значення – назва, яку бачитиме відвідувач:

{
  "web": "Веб",
  "web/php-html-css": "PHP, HTML, CSS",
  "web/wordpress": "WordPress",
  "soft": "Софт",
  "soft/linux": "Linux"
}

Це єдиний список, який доведеться доповнювати вручну, і він же стане основою для меню категорій.

Крок 2. Файл даних теки

В Eleventy є зручна штука – файл даних теки. Якщо покласти в теку JSON-файл з тією самою назвою, що й тека, його дані отримають усі сторінки в ній і в усіх підтеках. Тому в web/wordpress/wordpress.json пишемо:

{
  "tags": "posts",
  "categorySlug": "web/wordpress"
}

Тепер кожна стаття з цієї теки автоматично знає свою категорію і потрапляє в загальну колекцію posts, а у front matter самих статей нічого про категорію писати не треба.

Крок 3. Колекція сторінок категорій

Найхитріше місце. Сторінка категорії має показувати статті не тільки свої, а й з дочірніх категорій, і ділити їх на сторінки по 12 штук. Вбудована пагінація Eleventy вміє розбивати лише один список, тож заздалегідь підготуємо у файлі eleventy.config.js колекцію, де кожен елемент – це одна готова сторінка якоїсь категорії:

export default function (eleventyConfig) {
  eleventyConfig.addCollection("categoryPages", function (collectionApi) {
    const perPage = 12;
    const byCategory = {};

    // найновіші статті першими
    for (const post of collectionApi.getFilteredByTag("posts").reverse()) {
      // стаття потрапляє у свою категорію і в усі батьківські
      const parts = post.data.categorySlug.split("/");
      parts.forEach((part, i) => {
        const slug = parts.slice(0, i + 1).join("/");
        (byCategory[slug] ??= []).push(post);
      });
    }

    const pages = [];
    for (const [slug, posts] of Object.entries(byCategory)) {
      const total = Math.ceil(posts.length / perPage);
      for (let n = 1; n <= total; n++) {
        pages.push({ slug, n, total, posts: posts.slice((n - 1) * perPage, n * perPage) });
      }
    }
    return pages;
  });
}

Якщо в «WordPress» 30 статей, у колекції з'являться три елементи для цієї категорії (12 + 12 + 6), а ці ж 30 статей потраплять і на сторінки батьківської категорії «Веб». Нова категорія підхоплюється автоматично, щойно в ній з'являється перша стаття.

Крок 4. Один шаблон для всіх категорій

Тепер створи в корені проєкту файл category.njk. Завдяки пагінації по одному елементу з нього згенерується окрема сторінка для кожного елемента колекції:

---
layout: base.njk
pagination:
  data: collections.categoryPages
  size: 1
  alias: cat
permalink: "/category/{{ cat.slug }}/{% if cat.n > 1 %}page/{{ cat.n }}/{% endif %}"
eleventyComputed:
  title: "{{ categories[cat.slug] }}"
---
<h1>{{ categories[cat.slug] }}</h1>

{% for post in cat.posts %}
  <article>
    <h2><a href="{{ post.url }}">{{ post.data.title }}</a></h2>
    {% if post.data.description %}<p>{{ post.data.description }}</p>{% endif %}
  </article>
{% endfor %}

{% if cat.total > 1 %}
<nav class="pagination">
  {% if cat.n > 1 %}
    <a href="/category/{{ cat.slug }}/{% if cat.n > 2 %}page/{{ cat.n - 1 }}/{% endif %}">← Новіші</a>
  {% endif %}
  <span>Сторінка {{ cat.n }} з {{ cat.total }}</span>
  {% if cat.n < cat.total %}
    <a href="/category/{{ cat.slug }}/page/{{ cat.n + 1 }}/">Старіші →</a>
  {% endif %}
</nav>
{% endif %}

У результаті з'являться сторінки /category/web/, /category/web/wordpress/, /category/web/wordpress/page/2/ і так далі – без жодного окремого файлу для кожної категорії. Саме цього бракувало моєму старому способу: тоді для посторінкової навігації доводилося створювати окремий шаблон і окрему колекцію на кожну категорію.

Крок 5. Посилання на категорію в статті

Раніше це була найбільша морока: я тримав окремий список категорій з посиланнями і перебирав його циклом. Тепер у кожної статті вже є categorySlug, а назва лежить у categories, тож у шаблоні статті достатньо одного рядка:

<a href="/category/{{ categorySlug }}/">{{ categories[categorySlug] }}</a>

У списку статей на сторінці блогу дані кожної статті лежать у post.data:

<a href="/category/{{ post.data.categorySlug }}/">{{ categories[post.data.categorySlug] }}</a>

Крок 6. Меню категорій

Меню будуємо з того самого _data/categories.json. Рівень вкладеності легко визначити за кількістю частин у шляху, а далі вже справа CSS – наприклад, дати дочірнім пунктам відступ:

<nav class="categories">
  {% for slug, title in categories %}
    <a class="level-{{ slug.split('/').length }}" href="/category/{{ slug }}/">{{ title }}</a>
  {% endfor %}
</nav>

Якщо ж хочеш справжнє вкладене меню з підсвіткою активного пункту, скористайся плагіном, про який я писав у статті меню в Eleventy через eleventy-navigation.

Як додати нову категорію

Скажімо, у «Веб» з'являється нова категорія «Drupal». Тепер це три кроки:

  1. Створити теку web/drupal і писати в ній статті.
  2. Покласти в неї файл drupal.json з "tags": "posts" і "categorySlug": "web/drupal".
  3. Додати рядок "web/drupal": "Drupal" у _data/categories.json.

Сторінка категорії, посторінкова навігація і посилання в статтях з'являться самі.

Ще більше можливостей Eleventy, наприклад власні шорткоди і фільтри, зібрані за позначкою Eleventy, а інші генератори – за позначкою Статичні сайти.

А як ти організовуєш статті на своєму статичному сайті – тегами чи категоріями? Пиши в коментарях 🙂