Я не фронтенд-разработчик. Я — Head of Data, и мой обычный стек — это Python, SQL, Airflow, dbt и BigQuery. У меня есть немного опыта в SEO, но он скорее теоретический — я понимаю принципы, много читал про техническое SEO, но никогда не занимался этим профессионально. Тем не менее, когда я решил сделать персональный сайт, я не хотел брать WordPress или Wix, нажать три кнопки и получить шаблонную страницу, которая выглядит как миллион других. Я хотел полный контроль — над кодом, над производительностью, над тем, как моё имя появляется в Google. И сделал всё с нуля.
Эта статья — не туториал и не гайд. Это рассказ о моём процессе: какие решения я принимал, почему именно такие, и что из этого вышло.
Почему не WordPress?
Давайте начнём с самого очевидного вопроса. WordPress — это 43% веба. Он бесплатный, есть миллион тем, плагинов, хостингов на любой бюджет. Почему не он?
Потому что WordPress — это динамический сайт. Каждый запрос — это PHP-скрипт, который соединяется с MySQL, собирает страницу на лету и отдаёт её. Для блога на три статьи это как стрелять из пушки по воробьям. Плюс — уязвимости: WordPress требует постоянных обновлений, патчей, защиты от ботов. Для сайта-визитки это лишняя головная боль.
Мне нужен был статический сайт — набор HTML-файлов, которые nginx отдаёт мгновенно, без базы данных, без серверной логики, без PHP. Просто файлы. Максимально быстро, максимально просто, максимально безопасно.
Выбор генератора: почему Eleventy
Когда ты выбираешь Static Site Generator (SSG), выбор огромный: Hugo, Jekyll, Gatsby, Next.js, Astro, Eleventy... Я перебрал несколько вариантов.
Hugo — написан на Go, молниеносно быстрый, но шаблоны на Go templates — это отдельный язык, который нужно учить с нуля. Документация есть, но для нетривиальных вещей начинаешь гуглить часами. Да, AI-ассистенты сейчас решают эту проблему — можно попросить написать любой шаблон. Но мне хочется хотя бы базово понимать, что происходит в коде. Потому что AI из глупого делает ещё глупее, а из умного — продуктивнее на 50–60%.
Next.js / Gatsby — React-based. Для персонального сайта с тремя страницами тащить за собой React, Webpack (или Vite), SSR/SSG конфигурацию и 200 МБ node_modules — это overkill. Я не строю SaaS-продукт, мне нужен блог.
Astro — интересный вариант, но для моего случая — слишком много «магии» под капотом. Острова, компонентная модель, собственный формат .astro — это круто для сложных проектов, но для персонального сайта с блогом мне хотелось чего-то проще и прозрачнее.
Eleventy (11ty) победил по нескольким причинам:
- Простота. Eleventy не навязывает фреймворк. Никакого React, Vue, Svelte — только шаблоны и контент. Ты пишешь Markdown, выбираешь шаблонизатор (Nunjucks, Liquid, Handlebars), и получаешь HTML. Всё
- Гибкость. Ты контролируешь каждый байт вывода. Нет «магических» страниц или роутинга — есть файлы и папки, которые становятся URL-ами. Интуитивно понятно
- Zero client JS по умолчанию. Eleventy не вставляет никакого JavaScript в готовые страницы. Весь JS — только то, что ты сам добавишь. Для скорости это критично
- ESM поддержка. Eleventy 3.x полностью поддерживает ES Modules — никаких
require/module.exports, чистый современный JavaScript - Data cascade. Система каскадных данных — когда JSON-файл в папке автоматически применяется ко всем файлам в ней — идеально подошла для мультиязычности
Мультиязычность: три языка без плагинов
Одно из главных требований — сайт должен работать на нескольких языках: украинский (основной) и английский (для международной аудитории).
Я не использую ни одного плагина для i18n. Архитектура банально проста:
- Украинские страницы лежат в корне
src/(URL:/) - Английские — в
src/en/(URL:/en/)
Каждая языковая папка имеет свой JSON-файл (например, en.json), который через data cascade добавляет переменную lang ко всем файлам в ней. Шаблоны используют `` для UI-переводов, а контент страниц просто пишется вручную на каждом языке.
Hreflang теги генерируются автоматически через eleventyComputed.js — каждая страница знает свои альтернативы. Sitemap включает все три языковые версии с правильными hreflang-аннотациями. Всё это — без единого внешнего плагина.
CSS: Tailwind 4.x и CSS-first подход
Для стилей я выбрал Tailwind CSS. Не потому что это модно, а потому что utility-first подход идеально ложится на шаблоны — ты видишь все стили прямо в HTML, без прыжков между файлами.
Tailwind 4.x изменил правила игры — конфигурация теперь живёт прямо в CSS-файле через @theme {}, без отдельного tailwind.config.js. Плагины подключаются через @plugin, а пути к шаблонам — через @source. Всё в одном файле main.css.
Кастомные компоненты (карточки с glass-эффектом, кнопки, градиентный текст) определены через @utility — это позволяет писать .glass-card или .btn-primary как обычные классы, но со всеми преимуществами tree-shaking от Tailwind.
Шрифты — Inter для текста, JetBrains Mono для кода. Оба — self-hosted как variable fonts в WOFF2, загруженные с font-display: swap, чтобы текст показывался сразу, а шрифт подгружался в фоне. Никаких запросов к Google Fonts CDN — меньше соединений, быстрее загрузка.
Сборка CSS — через @tailwindcss/cli, без PostCSS. At build time Tailwind трясёт дерево и оставляет только те классы, которые реально используются в шаблонах. Итоговый CSS — менее 15 КБ после минификации.
JavaScript: меньше — лучше
Никакого React, Vue, jQuery или какого-либо фреймворка. Весь JavaScript — один файл main.js, подключённый с defer.
Что он делает:
- Scroll animations —
IntersectionObserverдобавляет класс.revealк элементам, когда они попадают во вьюпорт. Плавные появления без библиотек - Mobile menu — открытие/закрытие бургер-меню
- Language switcher — переключение языка через простые
<a>ссылки (каждая языковая версия — отдельный URL, это canonical для SEO) - Engagement widgets — просмотры, лайки, рейтинг, комментарии —
fetch()к API
Весь JS минифицируется при сборке. Конечный размер — несколько килобайт.
Backend: Express + SQLite для engagement
Единственная динамическая часть сайта — это «engagement»: просмотры статей, лайки, 5-звёздочный рейтинг и комментарии. Для этого нужен API.
Я написал его на Express + better-sqlite3 — минималистичный сервер, который:
- Хранит данные в одном SQLite-файле (без MySQL, без PostgreSQL, без облачных баз)
- Использует WAL mode для быстрых чтений/записей
- Имеет prepared statements для всех запросов — никакого SQL injection, минимальный overhead
- Включает rate limiting (in-memory) против спама
- Имеет систему модерации комментариев (pending → approved)
Весь API — один файл server.js на ~450 строк. Работает как systemd сервис на сервере, nginx проксирует /api/ к нему. Почему SQLite, а не PostgreSQL? Потому что для блога с десятком статей мне не нужен отдельный database server. SQLite — это один файл на диске. Бэкап — одна команда cp. Всё.
Хостинг: Oracle VM + nginx + Cloudflare
Я не использую Vercel, Netlify или GitHub Pages. Сайт крутится на Oracle Cloud VM (Always Free tier — ARM64, 1/8 ядра OCPU, 1 ГБ RAM, 480 Мбит/с сеть). Абсолютный минимум, но для статического сайта — более чем достаточно. Бесплатно и с полным контролем над сервером.
Nginx — как веб-сервер, настроенный на максимальную производительность:
sendfile on,tcp_nopush,tcp_nodelay— минимизация системных вызововopen_file_cache— кеширование файловых дескрипторов, чтобы nginx не ходил на диск при каждом запросеssl_buffer_size 4k— меньший TLS-рекорд для более быстрого TTFBbrotli_static on+gzip_static on— nginx отдаёт заранее сжатые.brи.gzфайлы без затрат CPU в рантайме- Отдельные правила кеширования: статические ассеты —
immutable, max-age=31536000(1 год), HTML — короткий TTL чтобы обновления доходили до пользователей быстро
Cloudflare стоит перед nginx как CDN:
- HTTP/3 (QUIC) + 0-RTT — минимальная задержка
- Edge caching на 30 дней для HTML (с purge на каждом деплое)
- Early Hints (103) — браузер начинает подгружать CSS/JS ещё до получения HTML
- DDoS-защита из коробки
- SSL через Origin Certificate
Деплой: git push → production за 2 минуты
Весь деплой автоматизирован через GitHub Actions. Один git push в main запускает пайплайн:
- Build & Test — npm ci → build CSS → build Eleventy → compress assets → lint HTML
- Deploy — rsync готовой
_site/папки на сервер через SSH - Reload nginx —
sudo nginx -t && sudo systemctl reload nginx - Purge & Warm Cloudflare cache — purge_everything → wait → curl по всем URL из sitemap.xml
От пуша до обновлённого сайта — менее 2 минут. Никакого ручного вмешательства.
Скрипт компрессии (compress.mjs) проходит по всем файлам в _site/ и генерирует .br и .gz версии. Это означает, что nginx не тратит CPU на сжатие в рантайме — просто отдаёт готовые файлы. Результат: ~80% экономии на размере, нулевая нагрузка на сервер.
Сжатие и оптимизация
Я одержим скоростью (профессиональная деформация — когда работаешь с данными, миллисекунды имеют значение). Вот что я сделал:
Pre-compression. Скрипт compress.mjs генерирует Brotli (.br) и Gzip (.gz) файлы для всех HTML, CSS, JS, JSON, XML, SVG и TXT. Nginx отдаёт их через brotli_static и gzip_static — без каких-либо затрат CPU.
HTML minification. Eleventy минифицирует HTML при сборке через html-minifier-terser — убирает комментарии, лишние пробелы, минифицирует инлайн CSS и JS.
Image optimization. Eleventy Image shortcode генерирует AVIF + WebP + JPEG в нескольких размерах (640, 960, 1280px) с loading="lazy" и decoding="async". Браузер выбирает оптимальный формат и размер автоматически.
Service Worker. Кеширует критические ассеты при первом посещении. Повторные визиты — мгновенные. Офлайн-страница показывается, когда нет интернета.
Self-hosted шрифты. Inter и JetBrains Mono как WOFF2 variable fonts — один файл на шрифт вместо десятка отдельных. Без внешних HTTP-запросов.
Результат: TTFB ~30мс на Cloudflare edge, LCP < 1с, общий размер страницы — менее 50 КБ в gzipped. PageSpeed Insights показывает 99–100 для мобильных и десктопа.
Тестирование: 102 юнит-теста + E2E
Для персонального сайта это может показаться overkill, но тесты дают мне уверенность, что изменения ничего не ломают. А в эру AI не писать тесты — это просто хамство и неуважение даже к самому себе. AI генерирует тесты за секунды, тебе остаётся только запустить. Нет никакого оправдания.
Vitest (юнит-тесты):
- Валидация HTML — каждая сгенерированная страница проверяется на корректность
- i18n — все ключи переводов существуют для всех трёх языков
- Schema.org — JSON-LD разметка валидна и содержит правильные данные
- TTFB — реальные запросы к klimnyk.dev проверяют, что сервер отвечает < 200мс
Playwright (E2E):
- Навигация работает правильно на всех языковых версиях
- Мобильное меню открывается и закрывается
- Языковой переключатель ведёт на правильные страницы
- Мета-теги (title, description, og:*) присутствуют
Всего 102 юнит-теста и 62 E2E-теста. Запускаются в CI перед каждым деплоем.
SEO: не просто мета-теги
Как Head of Data я знаю цену органического трафика. SEO здесь — не afterthought, а одна из основных причин существования сайта.
- Schema.org — 7 типов JSON-LD разметки: WebSite, Person, BreadcrumbList, WebPage, BlogPosting, Blog, ProfilePage
- Hreflang — правильные языковые альтернативы + x-default
- Sitemap.xml — автоматически генерируется со всеми URL и hreflang
- robots.txt — правильно настроен, не блокирует CSS/JS
- llms.txt — файл для LLM-краулеров по стандарту llmstxt.org, обновляется автоматически
- GEO meta tags — citation_*, article:published_time, max-snippet
- Canonical URLs — каждая языковая версия — отдельная страница с canonical на себя
Почему не проще?
С AI я потратил один вечер на базовую структуру сайта и пару часов в неделю на написание статей и правки. Я мог бы сделать сайт на Notion, Tilda или WordPress за один вечер. Но тогда бы у меня не было:
- TTFB в 30мс
- Полного контроля над разметкой и SEO
- Автоматического деплоя из GitHub
- Нулевого счёта за хостинг
- Уверенности, что сайт выдержит любую нагрузку (это же статика!)
- Удовольствия от того, что сделал всё сам
Это персональный проект, и в нём есть кайф от инженерии. Да, я Head of Data, а не фронтенд-разработчик. Но технологии — это технологии, и мне нравится разбираться, как вещи работают под капотом.
Что дальше
Сайт живёт, деплоится автоматически, проходит тесты. Контент появляется когда есть что сказать. Я не ставлю себе цель публиковать каждую неделю — это личный блог, а не медиа-издание. Но каждая статья делается с таким же вниманием к качеству, как и сам сайт.
Код живёт на GitHub в приватном репозитории. Никаких секретов, никакой магии — просто хорошо продуманный стек, который делает свою работу.