Files
Dmitry Gusev 7dc431f78e
All checks were successful
deploy / deploy (push) Successful in 1m10s
security / security (push) Successful in 3m25s
docs: фиксация правил уникальности SEO title/description
2026-05-30 02:25:09 +03:00

12 KiB
Raw Permalink Blame History

anotherreflections.ru — v2 (Astro)

Проект

Главный сайт-портал ролевой группы «Иные Отражения». Переделан со старого WordPress 6.9 (тема darkness-10, 23 активных плагина) на статический Astro 5 + Content Collections + markdown.

Сервер

Параметр Значение
Хост web.hhivp.com (45.10.53.206 / 45.10.53.242)
Deploy path /opt/docker/sites/anotherreflections-ru-v2/ (git clone)
Git remote git.striker.su/striker/anotherreflections-website-v2 (ветка main)
Контейнер anotherreflections-ru-v2 (nginx:1.29-alpine)
Внутренний порт 80
Внешний порт 127.0.0.1:4084 (nginx-хост → proxy_pass)
nginx vhost /etc/nginx/conf.d/anotherreflections.ru
OG-image / лого public/og-image.png, public/logo*.svg

Cutover (2026-05-20)

Со старого WordPress (контейнер anotherreflections-ru на :4080) — на новый Astro-контейнер (:4084). Старый контейнер и БД оставлены для отката на 1-2 недели.

Откат за 1 минуту:

ssh striker@web.hhivp.com
sudo sed -i 's|127.0.0.1:4084|127.0.0.1:4080|g' /etc/nginx/conf.d/anotherreflections.ru
sudo nginx -t && sudo systemctl reload nginx

Бэкап перед cutover в /opt/backup/anotherreflections-pre-v2-cutover-20260521-024027/:

  • wp-site-tree.tgz (349 МБ) — весь /opt/docker/sites/anotherreflections-ru/
  • wp-db-anotherreflctions_ru.sql.gz (5.6 МБ) — mysqldump БД (на db.hhivp.com MySQL)
  • anotherreflections.ru.nginx — старый vhost (proxy 4080)

Также резерв-копия vhost в /opt/backup/nginx-bak/20260521-024433/anotherreflections.ru.bak.20260520-wp.

Стек

  • Astro 5 (6.3.6) + Content Collections — каждая .astro страница рендерится в dist/.../index.html при npm run build (SSG, без runtime JS-фреймворка).
  • @astrojs/rss — RSS-фиды (общий + per-category) под IPB RSS Importer.
  • @astrojs/sitemapsitemap-index.xml автоматически.
  • sanitize-html — очистка HTML тела поста для <content:encoded> в RSS.
  • sharp (devDep) — генерация OG-image PNG из SVG.

Структура

src/
├── content/
│   ├── posts/*.md          (50 постов 2009-2015, мигрированы из WP)
│   ├── pages/*.md          (6 страниц: O нас, Наши друзья, страницы про игры)
│   └── _categories.json    (10 категорий-справочник)
├── components/
│   ├── BrandMark.astro     (SVG-знак в шапке)
│   ├── SocialLinks.astro   (плавающая правая колонка: Telegram, ВК, ↑)
│   ├── Analytics.astro     (Яндекс.Метрика + GA, скрипты с type=text/plain
│   │                        активируются после consent)
│   └── CookieConsent.astro (152-ФЗ-баннер, ar-consent в localStorage+cookie)
├── layouts/BaseLayout.astro
├── pages/
│   ├── index.astro         (hero + лента всех постов)
│   ├── [slug].astro        (один пост или одна страница, slug совпадает с WP)
│   ├── 404.astro
│   ├── miry.astro          (8 игровых вселенных карточками)
│   ├── kontakty.astro      (email + Telegram + ВК)
│   ├── privacy.astro       (политика + кнопка «Отозвать согласие»)
│   ├── feed.xml.ts         (общий RSS, фильтр по RSS_CUTOFF)
│   ├── sitemap.txt.ts      (plain-text список всех URL)
│   └── category/
│       ├── [slug].astro
│       └── [slug]/feed.xml.ts   (per-category RSS)
├── lib/rss-helpers.ts      (рендер md→HTML для <content:encoded>)
├── styles/global.css       (тёмная тема, decorative ::before/::after звёзды)
└── consts.ts               (SITE_TITLE, WORLDS, ANALYTICS, CATEGORY_COLORS, plural())

public/
├── favicon.svg             (брендовый знак на тёмном фоне, оптимизирован под 16-32px)
├── favicon.ico             (32×32 PNG-in-ICO, генерируется из favicon.svg)
├── apple-touch-icon.png    (180×180 для iOS home screen)
├── logo.svg, logo-mark.svg
├── og-image.png            (1200×630 для расшаривания в TG/VK/Twitter)
├── og-image.svg            (исходник)
├── robots.txt              (allow all, Host для Yandex, AI-crawlers)
├── ai.txt                  (Train/Cite/Quote: yes)
├── llms.txt                (формат llmstxt.org)
└── wp-content/uploads/     (4 файла, перенесены со старого WP: header_bg + NY-baner)

scripts/
├── migrate-wp.mjs          (одноразовый: _wp-export.json → src/content/*.md)
├── build-og-image.mjs      (sharp → public/og-image.png из SVG)
└── build-favicon.mjs       (sharp → public/favicon.ico + apple-touch-icon.png из favicon.svg; `npm run build:favicon`)

Dockerfile          (multi-stage: node:22-alpine builds → nginx:1.29-alpine serves)
nginx.conf          (gzip, кэш _astro/ 1y, MIME application/rss+xml для feed)
docker-compose.yml  (контейнер на 127.0.0.1:4084)
.gitea/workflows/deploy.yml  (push в main → SSH-деплой на web)

RSS под IPB Importer

RSS_CUTOFF в src/consts.ts (по умолчанию 2026-05-20) — фид отдаёт только посты с pubDate ≥ cutoff. Архив 2009-2015 на сайте остаётся, в форумы через RSS Importer не вбрасывается. Чтобы добавить пост в фид — публикуем с датой ≥ cutoff и пересобираем.

RSS 2.0 strict: <pubDate> в RFC-822, <guid isPermaLink="true"> = URL поста (IPB дедуплицирует), <content:encoded> с CDATA + полным HTML тела, <lastBuildDate> обновляется при каждом build.

URLs:

  • /feed.xml — общий
  • /category/<slug>/feed.xml — по категории
  • Яндекс.Метрика 13938862 (с webvisor) и Google gtag GT-PH39R3X. ID-шки в src/consts.ts:ANALYTICS.
  • Оба скрипта в Analytics.astro имеют type="text/plain" data-cookieconsent="statistics" — браузер их не выполняет, пока пользователь не нажмёт «Принять» в баннере.
  • CookieConsent.astro активирует скрипты при согласии, сохраняет выбор в localStorage + cookie ar-consent=accept|deny (12 мес).
  • На /privacy/ кнопка «Отозвать согласие» — ставит ar-consent=deny одним кликом.

Деплой

Через CI/CD (Gitea Actions) — push в main:

  1. Workflow .gitea/workflows/deploy.yml стартует.
  2. SSH на web.hhivp.com с ключом из секрета SSH_PRIVATE_KEY (base64).
  3. git fetch + git reset --hard origin/maindocker compose build --pulldocker compose up -ddocker image prune -af --filter "until=168h".
  4. Verify-шаг: curl -sf -H "Host: anotherreflections.ru" http://127.0.0.1:4084/.

Секреты в Gitea (/repos/striker/anotherreflections-website-v2/actions/secrets): SSH_PRIVATE_KEY, SSH_HOST=web.hhivp.com, SSH_USER=striker, SSH_PORT=22. Deploy-ключ — ~/.ssh/anotherreflections-v2-deploy{,.pub} локально, .pub в ~striker/.ssh/authorized_keys на web.

Вручную (с локалки):

cd /opt/docker/sites/anotherreflections-ru-v2
git pull
docker compose build && docker compose up -d

База данных

В новой версии БД нет. Старый WP оставлен с БД anotherreflctions_ru (sic, с опечаткой) на db.hhivp.com (45.10.53.205, MySQL), user u_anotherreflections, prefix anm_. После 1-2 недель наблюдения за новой версией — старый WP-контейнер можно удалить, БД и snapshot тоже.

SEO-title и description — уникальность

Шаблон в BaseLayout.astro:16-17: title рендерится как ${title} — ${SITE_TITLE}, description falls back на SITE_DESCRIPTION. Если страница не передаёт свои title/description, получает дефолт → дубли в выдаче.

Правила (поддерживать при добавлении страниц):

  • Posts (src/content/posts/*.md) — обязателен description: в frontmatter (Zod schema: z.string().default(''), но пустой даёт fallback). В SEO-title [slug].astro подставляет год публикации; если есть другой пост с тем же title за тот же год — подставляется полная дата (DD месяц YYYY).
  • Pages (src/content/pages/*.md) — обязателен description:. [slug].astro берёт его напрямую.
  • Категории (category/[slug].astro) — description вычисляется автоматически из количества публикаций и диапазона лет.
  • Главная (index.astro) — свой description прописан явно.

SEO/AI файлы

  • public/robots.txt — статика, разрешено всё; явно перечислены GPTBot/ClaudeBot/Google-Extended/CCBot/PerplexityBot/anthropic-ai; ссылки на sitemap-index.xml и sitemap.txt
  • public/ai.txt — Train/Cite/Quote: yes (по спецификации spawning.ai)
  • public/llms.txt — формат llmstxt.org с описанием проекта, ключевыми страницами, форумами, RSS-фидами, контактами
  • src/pages/sitemap.txt.ts — генерирует plain-text список 68 URL при каждом билде
  • @astrojs/sitemapsitemap-index.xml + sitemap-0.xml XML-формат

Скриншоты-источники

В E:/Projects/ лежат финальные:

  • anotherreflections-logo.svg / .png — знак + надпись
  • anotherreflections-logo-mark.svg / .png — только знак

Локальная разработка

npm install
npm run dev      # → http://localhost:4321
npm run build    # → dist/ (статика)
npm run preview  # просмотр build
npm run migrate  # одноразовая миграция _wp-export.json → md (уже сделана)

История

  • 2026-05-20: v1 (WordPress 6.9 + darkness-10 + 23 плагина, контейнер на :4080) переделан на Astro 5. Новый репо git.striker.su/striker/anotherreflections-website-v2. Cutover в production через nginx proxy_pass swap. См. также memory/project_anotherreflections_main_site.md.
  • 2026-05-07: 6 IPB-форумов проекта мигрированы со str-u-01 на web.hhivp.com (порты 4091-4096). См. memory/project_anotherreflections_forums.md.