Я не фронтенд-розробник. Я — 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 як звичайні класи, але з усіма перевагами 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 VM.Standard.E2.1.Micro — x86_x64, 1 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 у приватному репозиторії. Ніяких секретів, ніякої магії — просто добре продуманий стек, який робить свою роботу.