Eleventy (11ty) на сервере: частые ошибки и решения
Eleventy на бумаге — самый простой генератор статики: без виртуального DOM, без обязательного React, минимум магии. На практике же именно эта «минимальность» подводит: 11ty почти ничего не делает за вас, поэтому любая мелочь — не тот input, забытый .eleventyignore, неверный pathPrefix — превращается в пустую страницу или 404 прямо на боевом сервере. Ниже — конкретные ошибки, с которыми сталкиваются при деплое Eleventy на VPS, и как их закрыть без танцев с бубном.
Содержание
- Сборка падает или отдаёт пустой `_site`
- Локально всё работает, на сервере — ENOENT и ошибки путей
- Сайт открывается, но CSS/JS не грузятся (404 на ассетах)
- nginx отдаёт 404 на вложенных страницах и не работает `permalink`
- Плагины изображений и Eleventy Image ломают сборку на VPS
- Инкрементальная пересборка (`--watch`/`--serve`) на сервере не для продакшна
- SSL, кэш и HTTPS-редиректы после переноса на новый домен/IP
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Сборка падает или отдаёт пустой `_site`
Первая проверка после npx @11ty/eleventy на сервере — что вообще оказалось в _site/. Три типичные причины пустой сборки:
1. Неверный input/output в конфиге. Если .eleventy.js (или eleventy.config.js в версии 2.x+) указывает не на ту директорию — сборка пройдёт без ошибок, но результат будет не там, где вы ожидаете:
// eleventy.config.js
export default function (eleventyConfig) {
return {
dir: {
input: "src",
output: "_site",
includes: "_includes",
data: "_data",
},
};
};
Проверьте, что src/ реально существует в репозитории и не попала в .gitignore по ошибке.
2. .eleventyignore перекрывает всё содержимое. Частый случай — скопировали .eleventyignore из другого проекта, а там был * или слишком широкий паттерн. Eleventy молча пропустит все файлы, которые попадают под правило, и сборка завершится «успешно» с пустым _site.
3. Node.js слишком старый. Eleventy 3.x требует Node.js 18+ (актуальные релизы уже тянут 20 LTS). На свежем VPS часто стоит системный Node 16 или 18 из репозитория дистрибутива — этого может не хватить:
node -v
# если меньше 18 — ставим через NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
После установки обязательно снесите node_modules и переустановите зависимости — старые бинарные зависимости под старый Node иногда остаются в кэше:
rm -rf node_modules package-lock.json
npm install
npx @11ty/eleventy
Локально всё работает, на сервере — ENOENT и ошибки путей
Классика: eleventy serve на macOS/Windows отрабатывает без вопросов, а на Linux-сервере падает с ENOENT: no such file or directory. Причины почти всегда одни и те же.
Регистр в путях. Linux — регистрозависимая файловая система. Если в шаблоне написано {% include "Layout.njk" %}, а файл называется layout.njk — на macOS сборка пройдёт, на Ubuntu упадёт. Проверьте единообразие регистра во всех include/layout:
# найти потенциальные конфликты регистра в _includes
find src/_includes -type f | sort -f | uniq -di
Пути с обратным слэшем. Если в .eleventyignore или конфиге пути прописаны через \ (типично для тех, кто разрабатывал на Windows) — на Linux они просто не сработают. Используйте только /.
Символические ссылки и кейс с node_modules. Если зависимости ставились локально и заливались архивом через rsync/scp вместе с проектом — часть бинарных модулей (типа sharp для обработки изображений в плагинах) собрана под другую архитектуру или ОС. Решение простое — никогда не переносите node_modules между машинами:
# на сервере, из чистого клона
git clone --depth 1 git@your-repo:site.git /var/www/site
cd /var/www/site
npm ci
npm ci (не npm install) — берёт версии строго из package-lock.json, что критично для воспроизводимой сборки на сервере.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверСайт открывается, но CSS/JS не грузятся (404 на ассетах)
Это либо проблема passthroughFileCopy, либо неверный pathPrefix.
Passthrough copy не настроен. Eleventy по умолчанию обрабатывает только шаблонизируемые файлы (.njk, .md, .html и т.д.). Обычные CSS, JS, картинки нужно явно прокинуть:
export default function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("src/css");
eleventyConfig.addPassthroughCopy("src/js");
eleventyConfig.addPassthroughCopy("src/images");
return { dir: { input: "src", output: "_site" } };
};
Проверить, что файлы реально попали в сборку:
find _site -name "*.css" -o -name "*.js" | head -20
Пусто — значит addPassthroughCopy не сработал или пути указаны неверно относительно input.
pathPrefix не совпадает с реальным URL. Если сайт живёт не в корне домена, а в поддиректории (example.com/blog/), Eleventy нужно явно об этом сказать — иначе абсолютные ссылки на CSS/JS будут указывать мимо:
export default function (eleventyConfig) {
return {
pathPrefix: "/blog/",
dir: { input: "src", output: "_site" },
};
};
И собирать с флагом:
npx @11ty/eleventy --pathprefix=/blog/
Для большинства VPS-деплоев, где сайт занимает весь домен, pathPrefix вообще не нужен — но именно попытка «на всякий случай» его прописать и потом забыть чаще всего ломает ассеты.
nginx отдаёт 404 на вложенных страницах и не работает `permalink`
Eleventy по умолчанию генерирует «красивые» URL: страница src/blog/post.md превращается в _site/blog/post/index.html, а не в post.html. Это значит, что серверу нужно уметь резолвить /blog/post/ в index.html внутри этой директории — стандартный try_files для SPA тут не подходит и не нужен, важнее правильный index:
server {
listen 80;
server_name example.com;
root /var/www/site/_site;
index index.html;
location / {
try_files $uri $uri/ $uri.html =404;
}
location = /404.html {
internal;
}
error_page 404 /404.html;
}
Если у вас кастомный permalink в front-matter (например, файл без index.html, а сразу .html), порядок в try_files важен — $uri.html должен идти после $uri/, иначе nginx попытается найти файл раньше директории и отдаст не ту версию.
Отдельная грабля — 404-страница Eleventy не подхватывается. Если в проекте есть src/404.md с permalink: /404.html, но nginx не настроен на error_page, посетитель увидит стандартную страницу ошибки nginx вместо кастомной. Настройка error_page 404 /404.html; выше как раз это чинит — но не забудьте пересобрать сайт и убедиться, что _site/404.html реально существует:
ls -la _site/404.html
Если у вас на сервере уже настроен реверс-прокси или SSL через nginx, стоит свериться с базовой конфигурацией — в статье про nginx как reverse proxy разобраны похожие грабли с try_files и заголовками.
Плагины изображений и Eleventy Image ломают сборку на VPS
Плагин @11ty/eleventy-img — самый частый источник проблем на серверах с малым объёмом RAM. Он использует sharp, который тянет нативные бинарники под конкретную архитектуру и тратит заметно памяти на конвертацию каждого изображения при сборке.
Симптом: сборка зависает или процесс убивается OOM killer’ом на VPS с 1-2 ГБ RAM при десятках изображений.
Проверить, что случилось именно это:
dmesg | grep -i "killed process" | tail -5
free -h
Решения по приоритету:
- Добавить swap, если его нет — это самый быстрый и надёжный способ пережить пиковую нагрузку при сборке, не трогая код проекта.
- Ограничить параллелизм sharp, задав
concurrencyв опциях плагина:
eleventyConfig.addPlugin(eleventyImagePlugin, {
formats: ["webp", "jpeg"],
widths: [400, 800, 1200],
outputDir: "./_site/img/",
});
и в переменных окружения перед сборкой:
UV_THREADPOOL_SIZE=2 npx @11ty/eleventy
- Пересобирать изображения не на боевом сервере, а в CI (GitHub Actions/GitLab CI), заливая на VPS уже готовый
_site/— это снимает нагрузку с продакшн-машины полностью и делает деплой воспроизводимым.
Если сервер тесноват именно по памяти для сборок с картинками, для этой конкретной задачи лучше на старте брать план с запасом, а не добавлять своп постфактум под каждый пиковый билд.
Инкрементальная пересборка (`--watch`/`--serve`) на сервере не для продакшна
Соблазн запустить npx @11ty/eleventy --serve через pm2 или systemd и держать его постоянно живым на VPS — плохая идея для боевого сайта, хотя формально это работает.
Причины:
- Встроенный dev-сервер Eleventy (Browsersync) не предназначен для продакшн-нагрузки — нет кэширования, сжатия, лимитов на соединения.
- При каждом изменении файла происходит пересборка всего проекта — это лишняя нагрузка CPU, которая никак не отражается на скорости отдачи посетителям.
- Нет TLS «из коробки» — вам всё равно придётся ставить nginx/Caddy перед ним как прокси, а тогда смысл в dev-сервере теряется.
Правильная схема для VPS — статика собирается один раз (локально, в CI или руками на сервере) и раздаётся веб-сервером напрямую из _site/:
# деплой: собрать и синхронизировать без dev-сервера
npx @11ty/eleventy
rsync -avz --delete _site/ /var/www/site/_site/
sudo systemctl reload nginx
Такой pipeline проще держать под systemd-таймером или git-хуком post-receive, чем городить постоянно работающий Node-процесс ради генератора статики. Если сравниваете подход в целом, у нас есть разбор деплоя статического сайта на сервере и типичных ошибок — многое из общей логики применимо и к Eleventy.
SSL, кэш и HTTPS-редиректы после переноса на новый домен/IP
Когда Eleventy-сайт переезжает на новый VPS или домен, отдельная категория проблем — не в самом генераторе, а вокруг него.
Certbot не выдаёт сертификат. Частая причина — DNS ещё не указывает на новый сервер, либо nginx не отдаёт файл верификации из _site/.well-known/acme-challenge/, потому что Eleventy этот путь не копирует по умолчанию (он не проходит через passthroughFileCopy, если вы явно не добавили .well-known). Добавьте отдельную location:
location /.well-known/acme-challenge/ {
root /var/www/site/_site;
}
Разница между Certbot и acme.sh для таких случаев разобрана отдельно — см. Certbot или acme.sh: что выбрать для сервера.
Кэш браузера/CDN отдаёт старую версию после деплоя. Eleventy по умолчанию не добавляет хэши в имена CSS/JS файлов (в отличие от Astro или Next.js со сборщиком). Если у вас настроено агрессивное кэширование в nginx, после каждого деплоя стоит либо версионировать статику вручную (style.css?v=2026083101), либо снижать Cache-Control для HTML при неизменном для статики:
location ~* \.(css|js|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache";
}
Это не «ошибка» Eleventy как такового, но именно из-за минимализма генератора её приходится решать руками, а не автоматически, как в сборщиках с хэшированием ассетов.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Какую версию Node.js ставить для Eleventy на VPS?
Для Eleventy 3.x нужен Node.js 18 или новее; на практике стабильнее всего работает текущий LTS (на конец августа 2026 это ветка 22.x). Ставьте через NodeSource или nvm, а не через устаревший пакет из репозитория дистрибутива.
Нужен ли PM2 для сайта на Eleventy?
Нет, если вы не запускаете dev-сервер постоянно (а делать это на проде не стоит, см. выше). PM2 полезен только если рядом крутится Node-процесс — например, вебхук для автосборки при пуше в git.
Почему сборка на сервере занимает намного дольше, чем локально?
Обычно это либо нехватка RAM/CPU на минимальном тарифе VPS при работе с изображениями через eleventy-img, либо npm install вместо npm ci — первый пересчитывает дерево зависимостей заново, второй берёт готовый package-lock.json.
Можно ли автоматизировать деплой Eleventy без CI/CD-сервиса?
Да, через git-хук post-receive прямо на VPS: пушите в bare-репозиторий на сервере, хук делает npm ci && npx @11ty/eleventy и синхронизирует _site/ в рабочую директорию nginx. Это проще, чем поднимать полноценный CI, если проект небольшой.
Что делать, если после деплоя часть страниц пропала из сборки?
Проверьте .eleventyignore и фронт-маттер файлов на permalink: false или eleventyExcludeFromCollections: true — эти параметры намеренно исключают файл из вывода, и их легко забыть, скопировав шаблон из другого поста.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →