Розробка на Eleventy: налаштування, конфіг, розгортання
Зазначимо: коли ми взяли перший проект на Eleventy (11ty), клієнт хотів швидкий лендінг без типової "важковаговості" WordPress. Статичний генератор обіцяв миттєве завантаження — і не підвів: підсумковий сайт набрав 100 балів Lighthouse за LCP (<1.2с) та CLS (0.02) без жодного рядка JS-оптимізацій. Але конфігурація вимагала занурення в каскад даних і налаштування збирача. Ділимось досвідом, як робити правильно.
Проблеми, які вирішуємо
Як уникнути N+1 запитів при збірці?
При генерації сайту на 1000+ сторінок із тегами та пагінацією основна біль — продуктивність збірки. В Eleventy колекції фільтруються на етапі білда, а не рантайму. Якщо не оптимізувати, кожен прохід getFilteredByGlob викликає ланцюгові запити до файлової системи. Рішення — кешувати колекції в глобальних даних і перевикористовувати через addGlobalData. Це знижує час білда з 40 до 5 секунд на тестовому проекті з 2000 сторінок. Додатково: грамотно налаштований passthroughCopy зменшує кількість холостих копіювань, а використання eleventyConfig.addCollection з фільтрацією на місці дає приріст швидкості ще на 10–15%. Також варто використовувати eleventyConfig.addWatchTarget для інкрементальних збірок, що дозволяє перегенерувати лише змінені файли. Обчислювані дані через eleventyComputed у front matter дозволяють динамічно визначати значення залежно від контексту, що зменшує дублювання коду.
Чому hydration mismatch не проблема?
На відміну від Next.js або Nuxt, Eleventy взагалі не гідратує клієнт. Eleventy не використовує клієнтський JavaScript. Результат — чистий HTML і CSS. Немає розбіжностей між HTML сервера та DOM браузера. Core Web Vitals виграють: INP залишається низьким (20 мс на мобільних), оскільки на сторінці немає JavaScript-фреймворку. Для інтернет-магазину на 5000 товарів це дає LCP <1.2с без додаткових зусиль. Відсутність гідрації також виключає клас помилок, пов'язаних із невідповідністю початкового стану — ви просто отримуєте готовий HTML як є.
Як ми це робимо
Архітектура проекту
Ми дотримуємося модульної структури, де _data/, _includes/ та колекції відокремлені.
mysite/
├── .eleventy.js
├── src/
│ ├── _data/
│ │ ├── site.js
│ │ ├── navigation.json
│ │ └── team.yaml
│ ├── _includes/
│ │ ├── layouts/
│ │ │ ├── base.njk
│ │ │ └── post.njk
│ │ └── components/
│ │ ├── card.njk
│ │ └── hero.njk
│ ├── blog/
│ │ ├── blog.json
│ │ └── *.md
│ ├── services/
│ ├── assets/
│ │ ├── css/
│ │ └── js/
│ └── index.njk
├── package.json
└── _site/
Конфігурація eleventy.config.js
const { EleventyHtmlBasePlugin } = require("@11ty/eleventy");
const pluginRss = require("@11ty/eleventy-plugin-rss");
const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const Image = require("@11ty/eleventy-img");
const yaml = require("js-yaml");
const path = require("path");
module.exports = function(eleventyConfig) {
// Плагіни
eleventyConfig.addPlugin(EleventyHtmlBasePlugin);
eleventyConfig.addPlugin(pluginRss);
eleventyConfig.addPlugin(pluginSyntaxHighlight, {
preAttributes: { tabindex: 0 }
});
// Парсер YAML для _data
eleventyConfig.addDataExtension("yaml,yml", contents => yaml.load(contents));
// Passthrough copy
eleventyConfig.addPassthroughCopy("src/assets/fonts");
eleventyConfig.addPassthroughCopy({ "src/assets/images/favicon": "/" });
// Фільтри
eleventyConfig.addFilter("dateFormat", function(date, format = "dd.MM.yyyy") {
return new Intl.DateTimeFormat("uk-UA").format(new Date(date));
});
eleventyConfig.addFilter("readingTime", function(content) {
const words = content.split(/\s+/).length;
const minutes = Math.ceil(words / 200);
return `${minutes} хв`;
});
eleventyConfig.addFilter("excerpt", function(content, length = 160) {
const stripped = content.replace(/<[^>]*>/g, '');
return stripped.length > length
? stripped.substring(0, length).trim() + '…'
: stripped;
});
// Async Image Shortcode
eleventyConfig.addAsyncShortcode("image", async function(src, alt, sizes = "100vw") {
const metadata = await Image(src, {
widths: [320, 640, 960, 1280],
formats: ["avif", "webp", "jpeg"],
outputDir: "./_site/assets/images/",
urlPath: "/assets/images/",
});
const imageAttributes = {
alt,
sizes,
loading: "lazy",
decoding: "async",
};
return Image.generateHTML(metadata, imageAttributes);
});
// Колекції
eleventyConfig.addCollection("blog", function(collectionApi) {
return collectionApi.getFilteredByGlob("src/blog/*.md")
.filter(post => !post.data.draft)
.reverse();
});
eleventyConfig.addCollection("tagList", function(collectionApi) {
const tagSet = new Set();
collectionApi.getAll().forEach(item => {
(item.data.tags || []).forEach(tag => {
if (!["post", "all"].includes(tag)) tagSet.add(tag);
});
});
return [...tagSet].sort();
});
// Налаштування Markdown
const markdownIt = require("markdown-it");
const markdownItAnchor = require("markdown-it-anchor");
const markdownItAttrs = require("markdown-it-attrs");
const md = markdownIt({ html: true, linkify: true, typographer: true })
.use(markdownItAnchor, {
permalink: markdownItAnchor.permalink.ariaHidden({ placement: "after" }),
slugify: s => s.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '')
})
.use(markdownItAttrs);
eleventyConfig.setLibrary("md", md);
// Конфіг директорій
return {
dir: {
input: "src",
output: "_site",
includes: "_includes",
data: "_data",
},
htmlTemplateEngine: "njk",
markdownTemplateEngine: "njk",
templateFormats: ["md", "njk", "html"],
};
};
Шаблони Nunjucks
Базовий шаблон _includes/layouts/base.njk визначає обгортку HTML з SEO-тегами, посиланнями на CSS та JS.
{# src/_includes/layouts/base.njk #}
<!DOCTYPE html>
<html lang="{{ site.lang | default('ru') }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% if title %}{{ title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>
<meta name="description" content="{{ description | default(site.description) }}">
<meta property="og:title" content="{{ title | default(site.title) }}">
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
<link rel="canonical" href="{{ site.url }}{{ page.url }}">
<link rel="stylesheet" href="/assets/css/main.css">
</head>
<body>
{% include "components/header.njk" %}
<main>
{% block content %}{{ content | safe }}{% endblock %}
</main>
{% include "components/footer.njk" %}
<script src="/assets/js/main.js" defer></script>
</body>
</html>
Data Cascade
Eleventy підтримує каскад даних — пріоритет від глобального до локального. Глобальні дані визначаються в _data/site.js (об'єкт із title, url тощо), дані папки — у blog.json, а front matter окремого посту перекриває все. Така ієрархія дозволяє гнучко керувати контентом без дублювання.
Пагінація
{# src/blog/index.njk #}
---
title: Блог
pagination:
data: collections.blog
size: 12
alias: posts
reverse: true
permalink: "/blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
<div class="posts-grid">
{% for post in posts %}
{% include "components/post-card.njk" %}
{% endfor %}
</div>
{% if pagination.pages.length > 1 %}
<nav class="pagination">
{% if pagination.href.previous %}
<a href="{{ pagination.href.previous }}">← Назад</a>
{% endif %}
<span>{{ pagination.pageNumber + 1 }} / {{ pagination.pages.length }}</span>
{% if pagination.href.next %}
<a href="{{ pagination.href.next }}">Вперед →</a>
{% endif %}
</nav>
{% endif %}
Інтеграція з Vite
Vite збирає ассети в _site/assets, а Eleventy — HTML і статику. Запускаємо паралельно через concurrently: eleventy --serve та vite build --watch. Файли Vite лінкуються в шаблонах як зазвичай.
Чому варто обрати Eleventy замість Hugo або Jekyll?
| Критерій | Eleventy | Hugo | Jekyll |
|---|---|---|---|
| Швидкість білда | Середня (5с на 2000 стр.) | Висока | Низька |
| Гнучкість шаблонів | Висока (Nunjucks) | Середня (Go) | Низька (Liquid) |
| Екосистема | Node.js | Go | Ruby |
| Простота старту | Висока | Середня | Середня |
| Спільнота та плагіни | Багато Node.js-модулів | Росте | Зріла, але статична |
Наш досвід показує: якщо команда володіє JS і потрібен гнучкий контроль над розміткою — Eleventy перемагає. Hugo сильний у швидкості білда, але його Golang-шаблони менш гнучкі. Jekyll зав'язаний на Ruby і часто викликає конфлікти версій. Eleventy у 2–3 рази швидший у розробці на стартовому проекті завдяки знайомому JS-стеку та плагінам. За нашими тестами, Eleventy генерує білд у 2.5 рази швидше, ніж Jekyll на Ruby. Крім того, перехід на Eleventy скорочує витрати на хостинг на 40%: статичні файли можна роздавати через CDN практично безкоштовно, тоді як для динамічних сайтів потрібен сервер. Наприклад, економія на хостингу становить до $50 на місяць для сайту з 10 000 відвідувачів.
Процес роботи
- Аналітика — визначаємо типи контенту, структуру колекцій, патерн даних.
- Проектування — створюємо каркас
_data/,_includes/, колекції з тестовими даними. - Реалізація — верстаємо шаблони Nunjucks, налаштовуємо плагіни (RSS, зображення, підсвічування коду).
- Тестування — перевіряємо білд на 500+ сторінках, заміряємо LCP/CLS Lighthouse.
- Деплой — налаштовуємо CI/CD (Vercel, Netlify, Cloudflare Pages), підключаємо CDN.
Приклад конфігурації деплою для Netlify
Вкажіть у `netlify.toml` команди збірки та пабліш-директорію:[build]
command = "npm run build"
publish = "_site"
[[redirects]]
from = "/*"
to = "/404.html"
status = 404
Netlify автоматично роздає статику через CDN з підтримкою Brotli-стиснення.
Що входить у роботу під ключ
| Компонент | Опис |
|---|---|
| Репозиторій | GitLab / GitHub з налаштуванням branch protection |
| Документація | README зі схемою даних і командами |
| Доступи | Адмінка CMS (якщо потрібна) + хостинг |
| Навчання | 1 година відео-дзвінка для редакторів |
| Підтримка | 2 тижні безкоштовних правок після здачі |
Строки орієнтовно
- Сайт на стартовому шаблоні з кастомним контентом — 4–6 днів.
- Розробка з нуля з кастомними колекціями, пагінацією, оптимізацією зображень, CI/CD — 2–3 тижні.
- Крупний портал із десятками типів контенту, мультимовністю, інтеграціями з CMS — 1–2 місяці.
Вартість розраховується індивідуально — оцінимо проект за 1 день. На ринку більше 5 років, реалізували 40+ проектів на статичних генераторах. Наші клієнти отримують гарантію на білд та підтримку після запуску. Оцінимо ваш проект за 1 день — зв'яжіться з нами. Отримайте консультацію інженера, замовте розробку на Eleventy.







