SEO-оптимизация CareerClue — дизайн
Дата: 2026-07-18
Цель: рост органического трафика из Google и Yandex.
Сайт: Docusaurus 3.10, блог из 293 постов (mock-interview разборы, Q&A формат), ru, https://careerclue.vercel.app.
Контекст и решения
Зафиксировано на этапе brainstorming:
- Цель — комплексный рост органического трафика (техническое SEO + on-page).
- Домен — остаёмся на
careerclue.vercel.app, кастомный домен вне scope. - Backfill — полный, все 291 постов (без description) обрабатываются.
- Title — НЕ трогаем (permalink и так из имени файла; риск/diff не оправдан).
- OG-картинки per-post — отложены (Фаза 4, опционально).
- FAQPage/QAPage schema — пропускаем (Google с 2023 отдаёт FAQ rich results только gov/health; ROI низкий).
Что уже работает (проверено в build/)
BlogPostingJSON-LD эмитится Docusaurus 3.x автоматически (headline, description, url, dates).canonical,og:*,twitter:*присутствуют.sitemap.xmlгенерируется (preset-classic), но безlastmod.- Yandex Metrica +
yandex-verification+google-site-verificationподключены. - IndexNow скрипт (
scripts/indexnow-submit.js) есть. - Algolia поиск работает.
Дыры (что чиним)
- Meta description — только у 2 из 291 постов. У остальных Docusaurus авто-подставляет первый абзац, а он у всех начинается шаблоном «Сегодня мы разберём видео, в котором…» → почти-дубликаты meta description по всему сайту, не keyword-first, слабый CTR.
- Теги — 282 из 291 постов имеют
tags: [go]независимо от темы (65 из 66 frontend/react-постов помеченыgo). Ломает топикальную кластеризацию и тег-хабы. - robots.txt — отсутствует полностью.
- sitemap — без
lastmod(нет сигнала свежести для Yandex/Google). - BreadcrumbList — не эмитится для блога (только у docs). Упущенный breadcrumb rich result.
Вне scope (флаг)
Контент-mismatch: на части react/js-постов (~65) тела ответов местами написаны в контексте Go (артефакт генератора). Это не SEO-тюнинг, а регенерация контента — отдельный заход. В этом дизайне НЕ решается; теги/description будут выведены по реальной теме из title+тела, но сами тела не переписываются.
Приоритизированный план
Подход: технически-первый — быстрые глобальные code-правки применяются ко всем 293 постам сразу, затем LLM-backfill контента.
Фаза 0 — База (код)
0.1 static/robots.txt
User-agent: *
Allow: /
Sitemap: https://careerclue.vercel.app/sitemap.xml
Явная Sitemap: директива — основной способ, которым Yandex находит sitemap. Кладётся в static/, копируется в build как есть.
0.2 Sitemap lastmod
В docusaurus.config.ts заменить дефолтный sitemap preset-classic на явную конфигурацию:
sitemap: {
lastmod: 'date',
changefreq: 'weekly',
priority: 0.5,
},
Опция lastmod: 'date' (поддержана @docusaurus/plugin-sitemap 3.6+) добавляет <lastmod> из даты поста.
Verify: npm run build → build/robots.txt существует и содержит Sitemap; build/sitemap.xml содержит <lastmod> в url-записях постов.
Фаза 1 — BreadcrumbList JSON-LD
Swizzle-wrap theme/BlogPostPage (--wrap, не eject). В обёртке через useBlogPost() получить metadata поста и вставить в <Head> второй <script type="application/ld+json"> со схемой:
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Главная", "item": "https://careerclue.vercel.app/"},
{"@type": "ListItem", "position": 2, "name": "Разборы", "item": "https://careerclue.vercel.app/blog"},
{"@type": "ListItem", "position": 3, "name": "<title поста>", "item": "<permalink поста>"}
]
}
BlogPosting уже эмитится — при swizzle проверить его полноту (author, image, datePublished); допилить только если поля пустые.
Verify: npm run build → пост содержит два JSON-LD блока; BreadcrumbList проходит валидатор schema.org (структурная проверка вручную/скриптом).
Фаза 2 — Контент-backfill (LLM)
2.1 Скрипт scripts/seo-backfill.js (Node, стиль как build-blog-index.js)
Для каждого blog/*.md*:
- Парсит frontmatter + тело.
- Idempotent: пропускает пост, если
descriptionуже есть (флаг--forceдля перегенерации). - Формирует промпт: title + первые ~2000 символов тела → просит вернуть строгий JSON:
{"description": "...", "tags": ["...", "..."]}
description: 150–160 символов, keyword-first, уникальный, ru, без обрыва.tags: тема из фиксированного словаря{go, frontend, react, javascript, typescript, system_design, backend}+ грейд{junior, middle, senior}если явно определим. Тема выводится по РЕАЛЬНОМУ содержанию title+тела, а не по старомуgo.
- Вызывает OpenRouter free-модель (
OPENROUTER_API_KEYиз env, тот же ключ что у Go-генератора; endpointhttps://openrouter.ai/api/v1/chat/completions). Retry + вращение free-моделей при ошибке/rate-limit. - Пишет
descriptionиtagsобратно во frontmatter, сохраняя остальные поля и тело без изменений.
Батч-стратегия: обрабатывать пачками (напр. по 25), git add -A && git commit после каждой успешной пачки — чекпоинт для отката. Лог прогресса + список fail-ов в конце.
2.2 Go-генератор (будущие посты)
В mock/chatgtp (usecase.go / промпт генерации) добавить в выдаваемый frontmatter description и реальный tags — чтобы новые посты из пайплайна не регрессировали к пустому description и [go]. Минимальная правка промпта + шаблона записи.
Verify: после прогона grep -c '^description:' blog/*.md* ≈ 291; распределение tags больше не 282×[go]; npm run build без ошибок frontmatter; выборочно 5 постов — description осмысленный и релевантный теме.
Фаза 3 — Тег-хабы + перелинковка
С реальными тегами Docusaurus авто-создаёт /blog/tags/<tag> — топикальные лендинги.
- Проверить, что тег-страницы попадают в
sitemap.xml. - Убедиться, что теги кликабельны в посте (стандартный blog theme — да).
- Опционально: короткие описания тегов через
blog/tags.yml(permalink + description на хаб).
Verify: build/blog/tags/<tag>/index.html существуют для каждого нового тега и присутствуют в sitemap.
Фаза 4 — OG-картинки per-post (ОТЛОЖЕНО)
Авто-генерация OG-картинок (title на брендовом шаблоне) на build. Не реализуется в этом заходе; вынесено как отдельная задача.
Off-page (действия пользователя, документируются)
- Подать
sitemap.xmlв Google Search Console и Yandex Webmaster. - После деплоя дернуть IndexNow (
npm run indexnow) для новых/изменённых URL. - Мониторить индексацию и Core Web Vitals.
Я подготовлю чеклист этих шагов; выполняет пользователь (доступ к панелям).
Порядок выполнения и критерии успеха
| Фаза | Артефакт | Критерий готовности |
|---|---|---|
| 0 | robots.txt, sitemap config | build содержит robots.txt + <lastmod> в sitemap |
| 1 | swizzled BlogPostPage | 2 JSON-LD блока в посте, BreadcrumbList валиден |
| 2 | seo-backfill.js + Go-правка | ~291 постов с description, теги по темам, build зелёный |
| 3 | тег-хабы | тег-страницы в sitemap |
| 4 | — | отложено |
Риски
- LLM-качество description/тегов — free-модели могут врать/выдавать невалидный JSON. Митигация: строгий JSON-парсинг + retry + вращение моделей + git-чекпоинты по батчам + выборочная ручная проверка.
- Массовый diff (291 файл) — только frontmatter меняется; тела не трогаются. Батч-коммиты дают гранулярный откат.
- Swizzle при обновлении Docusaurus —
--wrap(не eject) минимизирует поломки при апгрейдах.