Перейти к содержимому
SEO-линтер для CI

Проверь SEO раньше, чем Google

Nuxt-модуль, таб в DevTools и краулер для CI, который роняет пайплайн на SEO-регрессиях. Любой стек.

$npx nuxi module add @ranklint/nuxt
ranklint diff, демо-приложение из репозитория
$ranklint audit --url http://localhost:3000 --reporter json --output report.json

24 страницы · playwright chromium · 7.1s

$ranklint diff --base main --current report.json --reporter markdown
errormeta:title-required/bugs/title-missing — Page has no <title>
errormeta:description-required/bugs/description-missing — Page has no meta description
errorheadings:single-h1/bugs/h1-multiple — Page has 2 <h1> elements, expected exactly 1
warnimages:alt-required/bugs/images-bad — Image has no alt attribute
warnimages:dimensions-required/bugs/images-bad — Image has no explicit width/height
warnimages:no-lazy-above-fold/bugs/images-bad — Image in the viewport uses loading="lazy"
6 новых замечаний, из них 3 errorexit code 1 · пайплайн остановлен
43
встроенных правила
< 1 KB
клиентский рантайм, gzip
6
форматов отчёта
MIT
лицензия, открытый код
01 — что это

Линтер, а не оценка от 0 до 100

Ranklint читает тот HTML, который реально отдаётся роботу, и называет конкретную проблему: пустой title, битый canonical, сломанную иерархию заголовков, односторонний hreflang. Три поверхности — один набор правил.

Nuxt-модуль

sitemap.xml, robots.txt и типизированный JSON-LD из коробки. Ставится одной командой, конфиг не обязателен.

useJsonLduseRanklintIgnore

Таб в DevTools

Проблемы, структура заголовков и мета-теги текущего маршрута — прямо на дев-сервере, пока ты пишешь код.

live-проверкасветлая и тёмная

Краулер для CI

Обходит задеплоенный сайт через Chromium. Работает с любым стеком — Nuxt не нужен.

auditdiffwatch
02 — попробовать

Проверь свой HTML

Те же правила, запущенные прямо в этой вкладке. Вставь разметку страницы — линтер ответит здесь же, без сервера и без установки.

HTML страницыlevel 1
<!doctype html>
<html lang="en">
<head>
  <title>Pricing</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta property="og:title" content="Pricing">
  <meta property="og:description" content="Plans and prices for teams of every size.">
  <meta property="og:image" content="https://example.com/og/pricing.png">
</head>
<body>
  <h1>Pricing plans for growing teams</h1>
  <h3>What the team plan includes</h3>
  <img src="/plans.png">
  <h1>Frequently asked questions</h1>
</body>
</html>
03 — в редакторе

Видно на дев-сервере, а не через месяц

Таб SEO в Nuxt DevTools показывает текущий маршрут так, как его увидит робот: список проблем, дерево заголовков и все мета-теги. Панель обновляется при навигации на странице и при изменениях DOM, плюс есть кнопка Rescan.

  • У каждой находки есть селектор и подсказка, как её починить
  • 21 из 43 правил работают без сети, с теми же id и уровнями, что и в CI
  • JSON-LD валидируется в dev: предупреждения идут в консоль и в панель
Как подключить
SEO PagesComponentsPayload/bugs/heading-jump
1warn
headings:hierarchyУровень заголовка прыгает с h1 на h3
структура заголовков
h1Heading jump fixture main heading here
h3This level jumps over h2 on purpose← пропущен h2
04 — правила

43 правила, 9 категорий

У каждого правила есть id и уровень: error, warn, info/off. Уровни лежат в конфиге рядом с кодом, а не в чьей-то голове.

Справочник правил
ranklint.config.tsуровни переопределяются как в ESLint
export default defineRanklintConfig({
  rules: {
    'canonical:required': 'error',
    'http:ttfb-budget': ['warn', { p75: 600 }],
    'images:no-lazy-above-fold': 'off',
  },
})
05 — в CI

Решает код возврата, а не совещание

Квартальный краул превращается в бэклог, о котором все забывают. Ranklint проверяет каждый пул-реквест: audit собирает JSON-отчёт, diff сравнивает его с базовой веткой и роняет пайплайн только на новых ошибках. Старые долги не мешают мержить.

Новые замечания относительно main

--reporter markdown
уровеньправилоURLчто случилось
errormeta:title-required/bugs/title-missingPage has no <title>
errormeta:description-required/bugs/description-missingPage has no meta description
errorheadings:single-h1/bugs/h1-multiplePage has 2 <h1> elements, expected exactly 1
warnimages:alt-required/bugs/images-badImage has no alt attribute
warnimages:dimensions-required/bugs/images-badImage has no explicit width/height
warnimages:no-lazy-above-fold/bugs/images-badImage in the viewport uses loading="lazy"
Старые замечания в отчёте помечены, но пайплайн не роняют. exit 1

Форматы отчёта

markdownjsonjunithtmlgitlabgithub

junit кладётся в отчёты раннера, gitlab — это отчёт Code Quality, а github превращает каждое замечание в аннотацию в пул-реквесте.

Два шага в пайплайне

$ ranklint audit --url https://preview --reporter json --output report.json
$ ranklint diff --base main --current report.json --reporter markdown

Ревью не ловит SEO-регресс: в браузере всё выглядит целым. Код возврата ловит.

зоны одного домена
  • /маркетинг — Nuxt 4owner: self
  • /docsдокументация — статикаowner: self
  • /appкабинет — SPA за авторизациейowner: external

У каждой зоны свой список проверок и свой владелец: свою зону краулер обходит целиком, чужую — только HEAD-проверкой ссылок, которые в неё ведут.

06 — любой стек

Nuxt не обязателен

Краулер ходит по HTTP и рендерит страницы в Chromium через Playwright. Ему всё равно, что отдаёт сервер — Next, Django, WordPress или статика на CDN.

  • Профили придерживают медленное: Lighthouse на релиз, обычный аудит на каждый пул-реквест
  • Профиль Lighthouse подключается отдельной проверкой
  • Свои правила — обычная функция, живёт в репозитории
  • HTML-отчёт со сгруппированными замечаниями — для тех, кто не в терминале
07 — прод

После деплоя проверка не заканчивается

Прогон ranklint audit --url https://prod --mode monitor по расписанию обходит прод, складывает отчёты в историю и пишет, когда что-то сломалось.

Полевые данные CrUX

LCP, CLS и INP реальных пользователей по всему origin попадают в тот же отчёт, рядом с замечаниями линтера.

LCPCLSINP

Search Console

URL Inspection отдаёт вердикт индексации, coverageState и проблемы расширенных сниппетов для первых десяти страниц краула.

Алерты и история

Сообщение в Slack или Telegram при новом error, история отчётов и дашборд — на GitHub или GitLab Pages.

08 — дашборд

Каждый прогон монитора публикует дашборд

Один шаг после мониторинга, и каждый плановый прогон выкладывает статический сайт на GitHub или GitLab Pages. Команда видит ошибки по страницам, тренды и историю без терминала.

Дашборд последнего прогонаexample.com
Дашборд ranklint для example.com: 38 ошибок и 11 предупреждений в последнем прогоне, новые и исправленные проблемы с прошлого прогона, тренд ошибок и Lighthouse по страницам

Что видит команда

  • Чек-лист по каждой странице: что исправить, как и ссылка на документацию правила
  • Fix once: проблемы, которые повторяются по разделу или всему сайту, обычно это один шаблон
  • Тренды ошибок, Lighthouse по страницам и CrUX, если задан ключ
  • Архив прогонов с полными отчётами, новые и исправленные проблемы помечены

Одна команда, твой бренд

$ ranklint dashboard \
  --dir .ranklint/reports --out public
--logo--brand-name--accent--run-url

Свой логотип, название и акцентный цвет вместо наших. --run-url добавляет кнопку Run audit, которая открывает пайплайн в CI.

Как настроить дашборд
09 — подробно

Что такое SEO-линтер

SEO-линтер читает HTML, который сайт реально отдаёт, и показывает то, что стоит трафика: пропавший title, canonical, ведущий на редирект, заголовок, который прыгает с h1 на h3, одностороннюю пару hreflang. Идея та же, что у ESLint, только применённая к разметке, которую читают поисковики, а не к JavaScript, который уезжает в прод. На выходе не оценка от 0 до 100, а список конкретных проблем: правило, адрес страницы, селектор и подсказка, что с этим делать.

SEO-аудит в CI, а не в таблице

Обычно SEO-работа умирает в таблице. Краулер запускают раз в квартал, выгружают список из 400 проблем, и список гниёт, пока сайт продолжает выкатываться. Линтер переворачивает порядок: краул идёт на каждом пул-реквесте, отчёт превращается в diff, и смотреть нужно только на то, что изменилось с последней зелёной сборки. ranklint сохраняет JSON-отчёт как артефакт сборки, сравнивает с ним текущий прогон и публикует разницу комментарием. Набор правил и их severity лежат в репозитории рядом с кодом, поэтому изменение требований к SEO проходит то же ревью, что и любой другой коммит.

Как связаны audit и diff

Регресс-тесты SEO на каждый пул-реквест

SEO-регрессии редко выглядят драматично. Рефакторинг убирает canonical из одного шаблона, миграция CMS оставляет 40 страниц без описания, эксперимент добавляет noindex, и его забывают снять. Такие правки проходят код-ревью, потому что в браузере ничего не сломано. Краул, который идёт вместе с тестами, ловит их в тот же час. Severity задаётся на каждое правило, поэтому длина title может остаться предупреждением, пока пропавший canonical останавливает мердж.

Настройка правил и severity
Решение принимает код возврата, а не человек.

Nuxt SEO модуль без конфига

В приложении на Nuxt 4 модуль закрывает всё, что относится к рутине. Он отдаёт sitemap.xml из маршрутов приложения, robots.txt, который различает стейджинг и прод, и JSON-LD с валидацией по Schema.org прямо во время разработки, так что сломанный блок Product или FAQPage виден как предупреждение в консоли, а не как тихая потеря расширенных сниппетов. Каждый блок отключается, а отключённый блок не регистрируется и ничего не стоит в рантайме. Композаблы подключаются только там, где они нужны: useJsonLd для структурированных данных страницы, useRanklintIgnore для точечного отключения правил.

Обзор модуля

Любой стек: краул задеплоенного сайта

Краулер говорит по HTTP и ничего не знает про внутренности фреймворка. Достаточно указать URL: каждая страница рендерится в Chromium, поэтому клиентский контент оценивается так же, как его оценивает поисковик, а страницы, которые существуют только после гидрации, попадают в отчёт как проблема индексации. Один домен делится на зоны, когда под разными путями живут разные приложения, а профили держат медленные проверки в стороне от быстрого пайплайна. Отчёт выгружается в markdown, json, junit, html или комментарий в GitLab и GitHub, поэтому результат читается там, где команда уже работает.

Начать с CLI
10 — вопросы

Что обычно спрашивают

Чем это отличается от Lighthouse?
Lighthouse ставит балл за страницу. Ranklint обходит весь сайт и выдаёт список конкретных правил с id и уровнем — по ним можно фейлить сборку и вести дифф между ветками.
А от Nuxt SEO?
Nuxt SEO помогает сгенерировать теги. Ranklint проверяет то, что в итоге отдалось, — и делает это ещё и в CI. Они не конфликтуют.
У нас не Nuxt. Подойдёт?
Да. CLI-краулер работает по HTTP с любым задеплоенным сайтом. Без Nuxt теряются только модуль и таб в DevTools.
Не начнёт ли падать весь пайплайн?
Для этого и нужен ranklint diff — он роняет сборку только на новых ошибках относительно базовой ветки, тогда как голый audit выходит с кодом 1 на любой error. Запускай diff в пайплайне, и накопленный долг остаётся в отчёте.
Можно отключить правило на одной странице?
В Nuxt — да, через useRanklintIgnore внутри страницы. Больше нигде точечного переопределения нет: поставь правилу off в rules или убери адрес из краула через crawl.ignore.
Обязателен ли Playwright?
Он нужен краулеру, чтобы рендерить страницы в Chromium. Nuxt-модулю и табу в DevTools — нет.
Сколько это стоит?
Ничего. Ranklint бесплатен целиком — открытый код под лицензией MIT, без платных тарифов и лимитов.
Открыть на GitHub

Следующий SEO-регресс не доедет до прода

Поставь модуль, добавь два шага в пайплайн — и дальше решает код возврата.