Отметим: когда мы взяли первый проект на 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%.
Почему 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("ru-RU").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 сокращает расходы на хостинг на 40%: статические файлы можно раздавать через CDN практически бесплатно, тогда как для динамических сайтов нужен сервер.
Процесс работы
- Аналитика — определяем типы контента, структуру коллекций, паттерн данных.
- Проектирование — создаём каркас
_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.







