Основа для SEO-ориентированных статических сайтов: лендинги, корпоративные страницы, контентные проекты.
Идея простая: вся конфигурация проекта — в одном файле, все опциональные возможности честно включаются и выключаются, а сборка проверяет себя сама.
bun install
bun devНовый проект:
- клонировать репозиторий;
- заменить значения в
main.config.ts; - положить свои
public/favicon.svgи OG-картинку; - добавить страницы в
src/pages/(новая локаль страницы не требует — см. ниже); bun run verifyи деплой.
Правится только main.config.ts — там URL сайта, название,
локаль, Open Graph, цвета темы, подтверждения владения доменом, аналитика и
feature flags.
- контракт и типы —
src/config/types.ts; - проверки —
src/config/validate.ts.
Конфигурация проверяется на старте dev и build: некорректный URL, неверный
формат GTM ID, отсутствующая OG-картинка, короткий ключ IndexNow — сборка падает
сразу и перечисляет все проблемы разом.
Часть некорректных состояний не выражается в типах: включить аналитику без ID или IndexNow без ключа не даст TypeScript.
| Фича | Конфиг | Что появляется в dist/ |
|---|---|---|
| SEO-метатеги, Open Graph, JSON-LD | всегда | теги в <head> каждой страницы |
robots.txt |
всегда | robots.txt |
| Sitemap | всегда | sitemap-index.xml, sitemap-0.xml |
| Favicon pipeline | всегда | favicon.*, apple-touch-icon.png |
| Web App Manifest | features.manifest |
site.webmanifest + <link rel="manifest"> |
ai.txt |
features.ai |
ai.txt + строка в robots.txt |
llms.txt + markdown-двойники страниц |
features.llms |
llms.txt, /index.md, /en.md, строка в robots.txt |
| IndexNow | indexNow.enabled |
<key>.txt + отправка изменённых URL |
| Google Tag Manager | analytics.googleTagManager |
скрипт GTM |
| Yandex Metrika | analytics.yandexMetrika |
скрипт счётчика |
| i18n | карта i18n.locales |
маршруты по локалям, <html lang>, og:locale, hreflang, sitemap с alternate-ссылками |
Выключенная фича не оставляет ничего: ни файла, ни тега, ни ссылки на себя
из robots.txt.
По умолчанию всё опциональное выключено.
Список локалей — карта «код → BCP-47-тег»: ключ служит сегментом URL и ключом
словаря, значение уходит в <html lang>, og:locale, ai.txt и манифест.
Отдельных полей языка сайта нет — страница не может объявить один язык в
разметке и другой в Open Graph.
Роутинг — нативный i18n Astro, без пакетов. Дефолтная локаль (i18n.defaultLocale
в main.config.ts) не получает префикс в URL: /, а не /ru/. Остальные локали —
/en/. Словари строк — src/i18n/locales/<locale>.ts, реестр —
src/i18n/dictionaries.ts (он же выводит тип ключа перевода), доступ через
useTranslations(locale) из src/i18n. hreflang-ссылки (включая x-default) и
sitemap с alternate-записями собираются автоматически из карты локалей.
Добавить локаль — три места: новый файл в src/i18n/locales/, строка в
src/i18n/dictionaries.ts, строка код: "тег" в карте i18n.locales.
Страница, markdown-двойник, hreflang-альтернативы и запись в llms.txt
появятся сами: главная всех локалей — один модуль на rest-маршруте
src/pages/[...locale].astro, набор путей строится из карты.
Пропущенный ключ, код без словаря и опечатка в переводе — ошибки компиляции, а не голый ключ на проде; невалидный код или тег роняют сборку на старте.
Генерирует @dualmark/astro:
каждая статическая страница из staticPages (в astro.config.mjs) получает
markdown-версию (/index.md, /en.md), а llms.txt собирается из явно заданных
sections — не парсится из HTML и не обновляется сам при добавлении страницы,
как было раньше. Появилась новая статическая страница — добавить её в
dualmarkStaticPages и в llmsTxt.sections в astro.config.mjs. Появится первая
content collection — замапить её в dualmark({ collections }) там же.
Alternate-ссылка на markdown отдаётся тегом <link rel="alternate" type="text/markdown">
в <head> (проп markdownURL у Layout/SEO), не HTTP-заголовком: сайт
статический, рантайм-middleware пакета на проде не выполняется.
Файл верификации /<key>.txt стартер создаёт сам — руками класть его в public/
не нужно. Ключ получают на bing.com/indexnow/getstarted.
Чтобы прогнать пайплайн без реальной отправки: { enabled: true, key: "...", dryRun: true }.
bun dev # дев-сервер
bun run build # сборка в dist/
bun run preview # локальный просмотр собранного
bun run check # astro check — типы
bun run check:seo # build + проверка dist/
bun run verify # check + check:seobun run check:seo проверяет каждую страницу сборки: <title>, description,
canonical, набор Open Graph, валидность JSON-LD, <html lang>, количество <h1>,
noindex на 404 — и что все локальные ссылки на ассеты действительно
существуют в dist/.
Фавиконы генерируются из public/favicon.svg перед bun dev
(или вручную: bun run generate:favicons).
main.config.ts значения проекта — единственный файл под правку
astro.config.mjs сборка интеграций, i18n-роутинг, dualmark
.editorconfig табы, LF, финальная новая строка
src/
config/ контракт конфигурации и её валидация
i18n/ локали, словари, useTranslations/buildAlternates
integrations/ генерация robots.txt, ai.txt, manifest, IndexNow, dualmark-двойники
components/head/ метатеги, JSON-LD, счётчики аналитики
layouts/Layout.astro каркас страницы
pages/ [...locale].astro — главная всех локалей, 404.astro
styles/tailwind.css тема и семантические токены
scripts/ favicon pipeline, SEO-валидатор
Артефакты интеграций генерируются на этапе astro:build:done, поэтому в
astro dev их нет — смотреть после bun run build.
output: "static" — подходит любой статический хостинг: Vercel, Cloudflare Pages,
Netlify, nginx. Команда сборки bun run build, каталог dist.
Адаптер не подключён; он нужен только при переходе на SSR.
Правила репозитория — в AGENTS.md, специализированные скиллы —
в .agents/skills/starter-*/. Они едут вместе с репозиторием, поэтому клон сразу
получает контекст: архитектуру, правила фич, SEO, Tailwind, TypeScript и
definition of done.