Как мы делали этот сайт: 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 вернул три релевантных кластера:
- ozsm.ru: SmartCaptcha вместо reCAPTCHA — уже решённый 152-ФЗ-вопрос, не нужно изобретать заново.
- river-cruise: email-deliverability с DKIM/DMARC/SPF, rate-limiter, RFC 8058 List-Unsubscribe — готовые паттерны.
- hermes-stack: profile-relay и Docker-compose сетап — референс для инфра-части.
Следом — чтение 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):
- LightRAG-запрос — паттерны из прошлых проектов
- Brainstorm + spec-документ — все архитектурные решения зафиксированы письменно
- Writing-plans — план разбит на 7 фаз по 8-10 задач
- Каждая фаза — отдельный subagent с минимальным контекстом
- После завершения — автоматическая 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:
- Runner клонирует репозиторий
pnpm install --frozen-lockfilepnpm test— 229 vitest-тестов; если хоть один упал — деплой не происходитpnpm build— Astro генерирует статику + Pagefind строит индексdocker build— новый образ с готовымdist/- Caddy:
reload— переключает upstream на новый контейнер - Старый контейнер 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:
- Статика (
_astro/,/pagefind/,/fonts/):Cache-Control: public, immutable, max-age=31536000— кэшируется навсегда. Браузер не будет запрашивать ни разу повторно. - HTML-страницы:
Cache-Control: no-cache, must-revalidate— браузер проверяет сервер при каждом посещении, но получает304 Not Modifiedесли ничего не изменилось.
Итог: ни одного ручного cache-bust. Обновил компонент — новый хэш — браузер подхватил.
Решение 9: cookie banner с gated Яндекс.Метрикой
Почему нельзя просто подключить Метрику: GDPR и 152-ФЗ требуют согласия перед любым трекингом. Метрика — analytics cookie. Без согласия её нельзя загружать.
Реализация:
- При первом посещении — banner с тремя уровнями: Necessary / Analytics / Marketing.
- Если пользователь не дал согласия —
mc.yandex.ru/metrika/tag.jsфизически не загружается. Вwindow.ymнет ничего. - При
analytics=true— скрипт Метрики динамически добавляется в DOM черезdocument.createElement('script'). - Согласие сохраняется в localStorage с таймстампом и версией политики. Если политика изменилась — banner показывается повторно.
/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 стек:
- DKIM 1024-bit (beget,
t=sрежим — строгий) - SPF
redirect=beget.com - DMARC
p=quarantine pct=25— рампинг-план доp=reject - RFC 8058 List-Unsubscribe One-Click — в каждом письме, обязательно
- Unsubscribe URL — в каждом письме в footer
Верификация: 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 + реальные устройства.
Конкретные меры:
- Минимальный touch target: 44×44 px для всех кнопок, ссылок, иконок — WCAG 2.2 Success Criterion 2.5.8.
viewport-fit=coverв<meta viewport>— контент не уезжает за notch на iPhone.- Типографика:
text-baseна mobile (16px minimum — нет auto-zoom в Safari). - Навигация: hamburger-меню на mobile с
client:loadisland (Svelte 5) — остальная страница остаётся статичной.
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:
- вебштурм.рф DNS — кириллический домен, редирект на webshturm.ru. Регистрация и настройка DNS займут 1-2 дня.
- Яндекс.Вебмастер + Яндекс.Постмастер — верификация сайта и домена отправителя. Без этого нет точных данных по индексации в Яндексе.
- DMARC ramp до p=reject — сейчас
pct=25. Следующий шаг:pct=50→pct=100→p=reject. Рамп занимает 4-6 недель для безопасного перехода. - SMTP боевой переключатель — текущая настройка через beget. Потребуется возможность переключиться на резервный SMTP без изменения кода.
Если хочется так же
Чек-лист для повторения этого стека:
- Astro 5 + Svelte 5 — официальные шаблоны через
npm create astro@latest. Для island-интерактива добавить@astrojs/svelte. - Supabase — free tier достаточен для визитки с формами. Self-hosted при необходимости хранить данные в РФ.
- SmartCaptcha — регистрация в Яндекс.Cloud, бесплатно для частной разработки. Замените все Google reCAPTCHA.
- Self-hosted Gitea + Actions runner — достаточно VPS от 500₽/месяц. Runner устанавливается одной командой, токен регистрации через API.
- Discipline на knowledge layer — LightRAG open-source (Docker Compose), Obsidian бесплатно, memory-файлы — просто Markdown. Главное: писать перед началом задачи (запрос в LightRAG) и после завершения (сохранить решение).
Если у вас компания, которой нужен сайт-визитка с этим стеком — мы делаем такие проекты за 2-3 недели от брейнштурма до прода. Тот же стек, та же дисциплина, с CI/CD и knowledge layer под вашу инфру. Напишите нам.