Назад до блогу

Як я зробив цей сайт і чому обрав саме такий стек

10 хв читання технології особисте

Я не фронтенд-розробник. Я — 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) переміг з кількох причин:

  1. Простота. Eleventy не нав'язує фреймворк. Немає React, Vue, Svelte — тільки шаблони і контент. Ти пишеш Markdown, обираєш шаблонізатор (Nunjucks, Liquid, Handlebars), і отримуєш HTML. Все
  2. Гнучкість. Ти контролюєш кожен байт виводу. Немає «магічних» сторінок або роутингу — є файли і папки, які стають URL-ами. Інтуїтивно зрозуміло
  3. Zero client JS за замовчуванням. Eleventy не вставляє жодного JavaScript в готові сторінки. Весь JS — тільки те, що ти сам додаш. Для швидкості це критично
  4. ESM підтримка. Eleventy 3.x повністю підтримує ES Modules — ніяких require/module.exports, чистий сучасний JavaScript
  5. 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 animationsIntersectionObserver додає клас .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-рекорд для швидшого TTFB
  • brotli_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 запускає пайплайн:

  1. Build & Test — npm ci → build CSS → build Eleventy → compress assets → lint HTML
  2. Deploy — rsync готової _site/ папки на сервер через SSH
  3. Reload nginxsudo nginx -t && sudo systemctl reload nginx
  4. 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 у приватному репозиторії. Ніяких секретів, ніякої магії — просто добре продуманий стек, який робить свою роботу.

(0)

Коментарі (0)