Как мы делали этот сайт: Astro 5, LightRAG, 152-ФЗ и почему всё за 1 день

Веб Штурм
  • astro
  • supabase
  • 152fz
  • lightrag
  • performance
  • mobile-first
  • ci-cd
  • case-study

За один день — от первой строчки до прода. Astro 5, Supabase, SmartCaptcha, 100/100 mobile, 152-ФЗ из коробки, full-text search без бэкенда. Расскажу как использовали LightRAG как knowledge layer, какие 12 решений приняли и почему.

Зачем сайт компании в 2026

«Нам не нужен сайт, мы работаем по рекомендациям» — мы слышим это от клиентов постоянно. И всё же запустили webshturm.ru. Не ради галочки.

B2B-лиды через demonstrate-by-doing. B2B-клиент, прежде чем позвонить, читает. Он хочет понять, как подрядчик думает и что умеет. Сайт — это не «страница о нас», это работающий пример нашей работы: Lighthouse 100/100, 152-ФЗ-совместимые формы, автодеплой за 90 секунд. Если мы можем сделать это для себя — мы сделаем это для вас.

GEO/AEO: новый канал, который уже работает. В 2026 году AI-поиск (Яндекс.Нейро, ChatGPT, Perplexity, Google AI Overviews) обрабатывает более 20% запросов в RU-сегменте без единого клика на сайт. Ответы формируются из структурированных данных, BLUF-контента и FAQ-блоков. Отсутствие сайта с правильной разметкой — это не «не тратим деньги», это «отдаём трафик конкурентам с сайтом».

Process-discipline как trust-signal. Регулируемые отрасли — туризм, логистика, государственные тендеры — проверяют подрядчика по соответствию 152-ФЗ ещё до первого звонка. Публичная архитектура сайта с описанием наших практик (SmartCaptcha, double opt-in, server-only secrets) — это не маркетинг, это технический due diligence.

День -1: брейншторм через LightRAG и память проектов

Работу мы начинаем не с чистого листа. Перед каждой задачей — lightrag_query(mode='mix').

В случае webshturm.ru запрос был: «Какой стек мы применяли для SSG с 152-ФЗ-совместимыми формами? Какие решения принимали по email-deliverability? Какие паттерны из ozsm.ru, river-cruise и hermes-stack применимы здесь?» За секунды knowledge-graph вернул три релевантных кластера:

Следом — чтение memory-файлов из ~/.claude/projects/k--webshturm/memory/ (~200 файлов). Там накоплены feedback-правила: «Beget SMTP: CLI PHP=5.6, FastCGI=7.3 с OPcache — не путать»; «На Windows bash hooks читают stdin с CRLF — обязательно tr -d '\r'»; «Lazy CSS bundle без полного critical CSS — CLS Poor». Эти правила работают как pre-flight checklist: проблема, которую мы уже решали однажды, второй раз не случается.

Сам процесс запускается через /штурм (skill superpowers:brainstorming):

  1. LightRAG-запрос — паттерны из прошлых проектов
  2. Brainstorm + spec-документ — все архитектурные решения зафиксированы письменно
  3. Writing-plans — план разбит на 7 фаз по 8-10 задач
  4. Каждая фаза — отдельный subagent с минимальным контекстом
  5. После завершения — автоматическая task-карточка в Obsidian-vault

Итог: от первой строчки spec до pnpm build без ошибок — один рабочий день.

12 решений

Решение 1: Astro 5 SSG, не Next.js и не SvelteKit

Выбор: Astro 5 с island-архитектурой для интерактивных компонентов на Svelte 5 runes.

Почему не Next.js: Next.js — мощный фреймворк, но по умолчанию добавляет JavaScript на каждую страницу. Для сайта-визитки с 33 страницами преимущественно статического контента это избыточно. RSC и App Router усложняют mental model без реальной пользы для такого проекта.

Почему не SvelteKit: SvelteKit — наш основной выбор для клиентских веб-приложений (оба флота работают на нём). Но SvelteKit предполагает server-side rendering по умолчанию, тогда как нам нужен чистый SSG с полной статической генерацией.

Почему Astro: Zero JS на не-интерактивных страницах — фактически. Интерактивные острова (client:load, client:idle, client:visible) включают гидратацию только там, где нужна. Встроенные content collections с Zod-валидацией позволяют работать с Markdown/MDX без дополнительного CMS. Image component генерирует WebP/AVIF и добавляет width/height автоматически.

Минусы: меньше готовых UI-компонентов, чем в экосистеме React. Меньше комьюнити для edge-кейсов. Если завтра понадобится сложное server-side состояние — придётся мигрировать.


Решение 2: 100/100 на Lighthouse mobile — 6 конкретных оптимизаций

Результат: 100/100 по всем четырём осям (Performance, Accessibility, SEO, Best Practices) на mobile. LCP стабильно ниже 1.5 сек на симуляции mobile 3G.

Шесть конкретных решений, которые это обеспечили:

1. Zero JS гидратация на статических страницах. Стартовая страница, страницы услуг, блог — чистый HTML без единого байта клиентского JavaScript. Браузер не ждёт JS-бандл для First Contentful Paint.

2. Preload шрифтов с font-display: swap. Критические шрифты объявлены в <head> через <link rel="preload">. font-display: swap гарантирует, что текст отображается сразу системным шрифтом, а кастомный загружается асинхронно без layout shift.

3. Astro Image для WebP/AVIF. Встроенный <Image /> компонент автоматически конвертирует изображения в WebP и AVIF, добавляет width и height, генерирует srcset. Ноль Cumulative Layout Shift от изображений.

4. Tailwind 4 critical CSS inline в <head>. Tailwind 4 инлайнит только используемый CSS в <style> тег в <head> на критическом пути. Ни один рендер не блокируется внешним CSS-файлом.

5. Prefetch links на hover, не на load. <ViewTransitions /> + prefetch на события hover и focus — страницы предзагружаются только когда пользователь собирается перейти. Не при загрузке списка статей.

6. Pagefind грузится lazy только на /search. JS-клиент Pagefind (~80 KB) подключается динамически только на странице поиска. На всех остальных страницах его нет в DOM.


Решение 3: 152-ФЗ из коробки — 5 пунктов

Контекст: 152-ФЗ «О персональных данных» требует, чтобы инструменты, получающие ПД пользователей, обрабатывали эти данные на серверах в РФ или в странах с адекватным уровнем защиты ПД. Google reCAPTCHA — серая зона: данные уходят на серверы Google в США. При целенаправленной проверке штраф за повторное нарушение — от 1 до 6 млн ₽.

Пять решений:

1. SmartCaptcha вместо reCAPTCHA. Яндекс SmartCaptcha — российский резидент. Данные пользователя (IP, fingerprint) обрабатываются на серверах Яндекса в РФ. Drop-in замена: тот же паттерн с sitekey + server-side verify, тот же UX. Бесплатно для некоммерческих проектов.

2. Cookie-consent 3-уровневый. Necessary (технические cookies, без согласия), Analytics (Яндекс.Метрика — только при analytics=true), Marketing (пустой — зарезервирован). Согласие хранится в localStorage и дополнительно логируется в Supabase для аудита.

3. Newsletter с double opt-in. Пользователь вводит email → получает письмо с токеном → переходит по ссылке → статус меняется с pending на confirmed. В базу попадают только подтверждённые адреса. GDPR-совместимо, 152-ФЗ-совместимо.

4. Согласие на обработку ПД с явным чекбоксом. Форма контактов требует явного [x] Согласен с политикой обработки персональных данных без pre-checked состояния. Отправка без согласия заблокирована на клиенте и валидирована на сервере.

5. /privacy + /cookie-settings страницы. Политика конфиденциальности — реальный документ с реквизитами ООО, не шаблон из интернета. Cookie-settings позволяют отозвать согласие в любой момент — обязательное требование.


Решение 4: Supabase для динамики, всё остальное — статика

Принцип: если страница не изменяется при каждом запросе — это статика. Если изменяется — Supabase.

Что в статике: весь контент (блог, кейсы, услуги, стек) — Markdown-файлы, компилируются в HTML при сборке. Нет CMS, нет API для контента, нет кэша второго уровня.

Что в Supabase: newsletter_subscriptions (email + статус + токен), cookie_consent_log (аудит согласий), contact_leads (заявки через форму контактов). Postgres 17 + Row Level Security — каждая строка доступна только через server endpoint с service_role ключом.

Почему именно Supabase: managed RLS без написания middleware, встроенные миграции через CLI, PostgREST REST API для server endpoints без ORM, Edge Functions для double-opt-in токенов, встроенный auth (пригодится для admin-панели). SSH-туннель на dev-окружении позволяет работать с production-схемой локально без VPN.

Минусы: Supabase self-hosted требует поддержки инфраструктуры. Для небольшого проекта это overhead; в следующий раз для MVP-визитки рассмотрим hosted Supabase free tier.


Решение 5: все секреты живут на сервере

Принцип: браузер не должен видеть ни одного приватного ключа, пароля или токена.

Astro server endpoints (src/pages/api/*) — это Node.js-функции, которые запускаются на сервере и возвращают клиенту только результат. ENV-переменные живут в systemd unit — там, где нет process.env из браузерного контекста.

Пример endpoint’а для newsletter:

// src/pages/api/newsletter.ts (Astro server endpoint)
import type { APIRoute } from 'astro';
import { supabaseAdmin } from '../../lib/supabase-admin';

export const POST: APIRoute = async ({ request }) => {
  const { email, captchaToken } = await request.json();

  // SmartCaptcha verify: secret key остаётся на сервере
  const captchaRes = await fetch('https://smartcaptcha.yandexcloud.net/validate', {
    method: 'POST',
    body: new URLSearchParams({
      secret: import.meta.env.SMARTCAPTCHA_SECRET,
      token: captchaToken,
    }),
  }).then(r => r.json());

  if (captchaRes.status !== 'ok') {
    return new Response('Captcha failed', { status: 400 });
  }

  // Supabase insert: service_role key тоже только на сервере
  const { error } = await supabaseAdmin
    .from('newsletter_subscriptions')
    .insert({ email, status: 'pending' });

  if (error) return new Response('DB error', { status: 500 });

  return new Response('OK');
};

Клиент видит только SMARTCAPTCHA_SITEKEY (public) и Supabase anon-key с RLS. Ни SmartCaptcha secret, ни service_role, ни SMTP-пароль физически не попадают в бандл. DevTools → Network: там только публичные ключи.


Решение 6: Pagefind для full-text search без бэкенда

Проблема: full-text поиск обычно требует либо платного SaaS (Algolia, Typesense Cloud), либо собственного поискового сервера с холодным стартом и поддержкой.

Решение: Pagefind — инструмент, который запускается как часть astro build и создаёт статический поисковый индекс из всех HTML-страниц:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import pagefind from 'astro-pagefind';

export default defineConfig({
  integrations: [pagefind()],
});

При сборке Pagefind сканирует все dist/**/*.html, токенизирует текст, строит перевёрнутый индекс и кладёт его в dist/pagefind/. JS-клиент (~80 KB) подгружается lazy только при переходе на /search. Поддерживает русскую морфологию (стем-алгоритм Snowball).

Ноль поисковых серверов, ноль операционных затрат, ноль холодного старта. Единственный минус — индекс обновляется только при следующей сборке: если опубликовать статью без деплоя, поиск её не найдёт. Для нашего сценария (деплой при каждом коммите) — не проблема.


Решение 7: автодеплой через Gitea Actions — 90 секунд от commit до live

Инфра: self-hosted Gitea на 185.179.188.145 + Gitea Actions runner на той же машине, что и прод.

Workflow при push в master:

  1. Runner клонирует репозиторий
  2. pnpm install --frozen-lockfile
  3. pnpm test — 229 vitest-тестов; если хоть один упал — деплой не происходит
  4. pnpm build — Astro генерирует статику + Pagefind строит индекс
  5. docker build — новый образ с готовым dist/
  6. Caddy: reload — переключает upstream на новый контейнер
  7. Старый контейнер graceful drain — запросы, которые уже идут, не обрываются

Полный цикл: 90 секунд. Downtime: ноль.

Для npm-пакетов (@webshturm/pdfme-plugins-cruise-pack и другие): push тега v* → npm publish в Gitea Packages. PAT через basic-auth, секрет NPM_TOKEN в Gitea secrets (не GITEA_* — зарезервировано).


Решение 8: версионирование статики — Astro content-hash + Cache-Control immutable

Проблема: браузеры агрессивно кэшируют статику. После деплоя пользователь может увидеть старую версию из кэша.

Решение Astro: все JS и CSS бандлы получают content-hash в имени файла при сборке: _astro/Layout.BkJnXy2A.css, _astro/Page.abc123.js. Если файл изменился — изменился хэш — браузер запрашивает новый файл.

Cache-Control стратегия в Caddy:

Итог: ни одного ручного cache-bust. Обновил компонент — новый хэш — браузер подхватил.


Почему нельзя просто подключить Метрику: GDPR и 152-ФЗ требуют согласия перед любым трекингом. Метрика — analytics cookie. Без согласия её нельзя загружать.

Реализация:

  1. При первом посещении — banner с тремя уровнями: Necessary / Analytics / Marketing.
  2. Если пользователь не дал согласия — mc.yandex.ru/metrika/tag.js физически не загружается. В window.ym нет ничего.
  3. При analytics=true — скрипт Метрики динамически добавляется в DOM через document.createElement('script').
  4. Согласие сохраняется в localStorage с таймстампом и версией политики. Если политика изменилась — banner показывается повторно.
  5. /cookie-settings позволяет отозвать любое согласие — Метрика выгружается из DOM (через ym(ID, 'destruct')).

Это не «написать документ о согласии». Это техническая гарантия того, что трекинг не происходит без разрешения.


Решение 10: newsletter с double opt-in и email deliverability

Схема данных (4 таблицы): newsletter_subscriptions (email, status, confirm_token), newsletter_sends (campaign, recipient, status), newsletter_suppressions (отписки + bounces), email_queue (outbound queue с retry).

Rate-limiter: beget SMTP ограничен по умолчанию — 30 писем в минуту, 1500 в час. Email-queue отправляет асинхронно, не в рамках HTTP-запроса пользователя.

Email deliverability стек:

Верификация: Mail.ru PASS, Google Postmaster PASS, Port25 PASS.

Почему это важно для клиентов: если email-deliverability настроена плохо, письма падают в спам — и это не проблема контента, это проблема инфраструктуры. Мы делаем это правильно для себя, чтобы знать все подводные камни до того, как столкнётся клиент.


Решение 11: Tailwind 4 mobile-first

Принцип mobile-first: базовые стили пишутся для экранов от 320px. Breakpoints md: и lg: расширяют до планшета и десктопа — не переопределяют.

Тестирование: iPhone SE (375px, самый маленький mainstream), Pixel 7 (412px), iPad (768px). Chrome DevTools Device Simulation + реальные устройства.

Конкретные меры:

Lighthouse Accessibility: 100/100. Отдельного «мобильного» кода нет — один codebase, один HTML, CSS адаптирует.


Решение 12: knowledge layer как часть процесса разработки

Это не «инструменты AI-разработки». Это способ накапливать знания между сессиями и не решать одну и ту же проблему дважды.

Три слоя:

LightRAG (semantic): каждое архитектурное решение, каждый инцидент, каждый закрытый проект — в knowledge-graph. Перед любой новой задачей: lightrag_query(mode='mix'). Запрос возвращает релевантные кластеры из всей базы — не только из текущего проекта. Когда мы делали webshturm.ru, мы нашли готовые паттерны из ozsm.ru, river-cruise и hermes-stack за секунды, не за часы.

Memory-файлы (project state): ~/.claude/projects/k--webshturm/memory/ — markdown-файлы с feedback-правилами, project-handoff, reference. Формат: MEMORY.md как индекс, отдельные файлы для каждого события. Читается в начале каждой сессии как pre-flight checklist.

Obsidian-vault (operational): каждый /штурм автоматически создаёт task-карточку в K:/obsidian-tasks/Задачи/. Spec-документ без task-карточки невидим в Канбан-дашборде. Канбан читается утром — видно что pending, что blocked, что active. Нет «потерянных» задач.

Для клиентских проектов: тот же knowledge layer. Паттерны одного клиента (с его согласия) не попадают в базу другого. Но наши собственные engineering-паттерны — общий фонд знаний, который накапливается и переиспользуется.

Что осталось

После запуска production 2026-05-03 несколько задач остаются в TODO:

Если хочется так же

Чек-лист для повторения этого стека:

  1. Astro 5 + Svelte 5 — официальные шаблоны через npm create astro@latest. Для island-интерактива добавить @astrojs/svelte.
  2. Supabase — free tier достаточен для визитки с формами. Self-hosted при необходимости хранить данные в РФ.
  3. SmartCaptcha — регистрация в Яндекс.Cloud, бесплатно для частной разработки. Замените все Google reCAPTCHA.
  4. Self-hosted Gitea + Actions runner — достаточно VPS от 500₽/месяц. Runner устанавливается одной командой, токен регистрации через API.
  5. Discipline на knowledge layer — LightRAG open-source (Docker Compose), Obsidian бесплатно, memory-файлы — просто Markdown. Главное: писать перед началом задачи (запрос в LightRAG) и после завершения (сохранить решение).

Если у вас компания, которой нужен сайт-визитка с этим стеком — мы делаем такие проекты за 2-3 недели от брейнштурма до прода. Тот же стек, та же дисциплина, с CI/CD и knowledge layer под вашу инфру. Напишите нам.