Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ Regression-only checks cover newly introduced indexing blocks, status changes,
metadata removal or edits, redirect changes, robots changes, and sharp sitemap
URL loss.

[Read the rule-by-rule documentation](docs/rules.md).
[Read the rule-by-rule documentation](docs/rules.md) ·
[Українська версія довідника правил](docs/rules.uk.md).

## Snapshots, lifecycle, and checkpoints

Expand Down
2 changes: 2 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SEO Crawl Audit rules

**English** | [Українська](rules.uk.md)

Every finding has a stable rule ID, severity, owner, evidence, remediation, and
fingerprint. Rules report concrete risks; the tool does not calculate a general
SEO score.
Expand Down
257 changes: 257 additions & 0 deletions docs/rules.uk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
# Правила SEO Crawl Audit

[English](rules.md) | **Українська**

Кожна знайдена проблема має стабільний ID правила, рівень критичності,
відповідального, докази, рекомендацію з виправлення та fingerprint. Правила
повідомляють про конкретні ризики; інструмент не обчислює загальний SEO-бал.

Рівні критичності:

- `error` — проблема, яка, ймовірно, перешкоджає обходу, індексації або доступу
та блокує звичайну перевірку регресій;
- `warning` — важлива проблема SEO або якості контенту;
- `info` — корисна інформація, яку варто переглянути, але яка типово не блокує
перевірку.

## Доступ, індексація та перенаправлення

### page-unreachable

Запит сторінки завершився невдало після обмеженої кількості повторних спроб.
Відповідальний: `developer`. Відновіть сторінку, виправте мережеву проблему або
приберіть посилання на URL, який свідомо виведено з експлуатації.

### http-error

Сторінка повернула HTTP 4xx або 5xx. Відповідальний: `developer`. Поверніть
успішну відповідь або налаштоване перенаправлення на чинну сторінку-заміну.

### robots-blocked

Сторінку заборонено відповідною групою правил у `robots.txt`. Відповідальний:
`developer`. Дозволяйте доступ лише тоді, коли пошукові роботи повинні обходити
цю сторінку.

### noindex

HTML містить meta-директиву `noindex`. Відповідальний: `seo`. В окремому аудиті
це інформаційне повідомлення, адже директива може бути навмисною.

### x-robots-noindex

HTTP-заголовок `X-Robots-Tag` містить `noindex`. Відповідальний: `developer`.
Переконайтеся, що заголовок додано навмисно; приберіть його, якщо сторінка має
індексуватися.

### redirect-loop

Ланцюжок перенаправлень повертається до вже відвіданого URL. Відповідальний:
`developer`. Розірвіть цикл і перенаправляйте одразу на кінцеву адресу.

### long-redirect-chain

Для переходу потрібно понад три перенаправлення. Відповідальний: `developer`.
Замініть проміжні переходи прямим перенаправленням.

### http-on-https-site

HTTPS-сайт містить внутрішній HTTP URL у результатах обходу або метаданих.
Відповідальний: `developer`. Послідовно використовуйте HTTPS.

## Канонічні URL

### missing-canonical

Канонічне посилання не знайдено. Відповідальний: `seo`. Додайте його, якщо
можуть існувати варіанти URL або дубльовані шляхи.

### invalid-canonical

Значення canonical неможливо перетворити на HTTP(S) URL. Відповідальний:
`developer`. Використайте чинний абсолютний URL або коректне відносне посилання.

### cross-domain-canonical

Canonical веде на інший origin. Відповідальний: `seo`. Переконайтеся, що таке
об’єднання між сайтами є навмисним.

### canonical-target-error

Ціль canonical входить до сканування, але недоступна. Відповідальний:
`developer`. Вкажіть успішну кінцеву сторінку, яку можна індексувати.

## Узгодженість sitemap

### sitemap-unavailable

Налаштований sitemap не вдалося завантажити або розібрати. Відповідальний:
`developer`. Відновіть доступний і коректний XML sitemap.

### noindex-in-sitemap

URL із sitemap позначено як `noindex`. Відповідальний: `seo`. Приберіть його із
sitemap або зробіть доступним для індексації.

### redirect-in-sitemap

URL із sitemap перенаправляє на іншу адресу. Відповідальний: `seo`. Додайте до
sitemap його кінцевий канонічний URL.

### orphan-sitemap-page

Сторінка із sitemap не має знайдених внутрішніх посилань. Відповідальний: `seo`.
Додайте корисну внутрішню навігацію або переконайтеся, що сторінку ізольовано
навмисно.

### crawlable-not-in-sitemap

Успішну доступну для обходу сторінку знайдено поза sitemap. Відповідальний:
`seo`. Додайте її до sitemap або свідомо виключіть.

### sitemap-url-count-drop

Порівняно з базою sitemap втратив щонайменше 20% і щонайменше п’ять URL.
Відповідальний: `seo`. Перевірте, чи немає випадкових виключень або помилок
генерування.

## Заголовки, описи та структура

### missing-title

Сторінка не має непорожнього HTML title. Відповідальний: `content`. Додайте
унікальний інформативний заголовок.

### duplicate-title

Кілька просканованих сторінок мають однаковий title. Відповідальний: `content`.
Розрізняйте сторінки відповідно до їхнього призначення.

### title-length

Title коротший за 10 або довший за 60 символів. Відповідальний: `content`.
Сприймайте цей діапазон як сигнал для перевірки, а не гарантію відображення в
пошуку.

### missing-description

Сторінка не має meta description. Відповідальний: `content`. Додайте корисний
опис там, де важливий текст у результатах пошуку.

### duplicate-description

Кілька сторінок мають однаковий description. Відповідальний: `content`.
Напишіть опис, який відповідає конкретній сторінці.

### description-length

Description коротший за 50 або довший за 160 символів. Відповідальний:
`content`. Перевірте зрозумілість тексту та ймовірність обрізання.

### missing-h1

Заголовок H1 відсутній. Відповідальний: `content`. Додайте чіткий основний
заголовок.

### multiple-h1

На сторінці більше одного H1. Відповідальний: `content`. Це інформаційне
повідомлення: перевірте ієрархію документа, не змінюючи її механічно.

## Посилання, мова, структуровані дані та контент

### broken-internal-link

Внутрішнє посилання веде на проскановану сторінку, яка недоступна або повернула
помилку. Відповідальний: `developer`. Відновіть, оновіть або приберіть
посилання.

### invalid-hreflang

Альтернативна версія має некоректний мовний тег BCP 47 або нерозпізнаний URL.
Відповідальний: `seo`. Виправте мову та цільову адресу.

### invalid-language

Значення мови в `html` не є коректним тегом BCP 47. Відповідальний:
`developer`. Правильно вкажіть мову документа.

### malformed-json-ld

Блок `application/ld+json` не є коректним JSON. Відповідальний: `developer`.
Виправте синтаксис JSON перед перевіркою словника.

### duplicate-content

Нормалізований видимий текст на кількох сторінках має однаковий SHA-256.
Відповідальний: `content`. Об’єднайте точні дублікати або суттєво розрізніть
їхній вміст.

### image-missing-alt

Зображення не має атрибута `alt`. Відповідальний: `content`. Додайте змістовний
альтернативний текст або порожній атрибут для декоративного зображення.

### low-word-count

Сторінка має менше ніж 200 видимих слів. Відповідальний: `content`. Це лише
сигнал для перевірки: лаконічна сторінка може бути цілком доречною.

### missing-open-graph

Відсутні Open Graph title, description або image. Відповідальний: `content`.
Додайте поля, якщо важливий вигляд посилання в соціальних мережах.

### missing-twitter-metadata

Відсутні Twitter card, title, description або image. Відповідальний: `content`.
Додайте поля, якщо важливий вигляд посилання на цій платформі.

## Правила лише для регресій

### robots-changed

Змінився hash вмісту `robots.txt`. Перевірте конкретну зміну в розгортанні.

### page-missing

Сторінку з базового знімка не перевірено в поточному знімку.

### status-regression

Сторінка, яка раніше була доступною, тепер повертає помилковий HTTP-статус.

### new-noindex

Сторінка отримала директиву `noindex` у meta або `X-Robots-Tag`. Це рівень
`error`, оскільки випадкова втрата індексації є ризиком високої критичності.

### title-removed

Title, який раніше був присутній, тепер відсутній.

### title-changed

Title змінився. Перевірте, чи передбачена ця зміна контентом або розгортанням.

### description-removed

Description, який раніше був присутній, тепер відсутній.

### canonical-removed

Canonical, який раніше був присутній, тепер відсутній.

### canonical-changed

Ціль canonical змінилася. Нова ціль на іншому origin підвищує критичність
проблеми до `error`.

### h1-removed

Сторінка, яка мала H1, тепер його не має.

### redirect-changed

Кінцева адреса перенаправлення змінилася. Нова адреса на іншому origin підвищує
критичність проблеми до `error`.
2 changes: 1 addition & 1 deletion examples/quotes-toscrape-report.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
}
function render(){
const text=copy();const query=search.value.trim().toLowerCase();lastFiltered=report.issues.filter((issue)=>{if(activeTab&&issue.lifecycle!==activeTab)return false;if(severity.value&&issue.severity!==severity.value)return false;if(rule.value&&issue.rule!==rule.value)return false;if(template.value&&issue.template!==template.value)return false;if(owner.value&&issue.owner!==owner.value)return false;if(!query)return true;const localized=issueText(issue);return [issue.url,issue.template,issue.rule,issue.message,issue.remediation,localized.rule,localized.message,localized.remediation,issue.owner,valueText(issue.evidence),valueText(issue.before),valueText(issue.after)].join(" ").toLowerCase().includes(query)});const rowsPerPage=Number(pageSize.value);const pageCount=Math.max(1,Math.ceil(lastFiltered.length/rowsPerPage));currentPage=Math.min(currentPage,pageCount);const visible=lastFiltered.slice((currentPage-1)*rowsPerPage,currentPage*rowsPerPage);tbody.replaceChildren();
for(const issue of visible){const localized=issueText(issue);const row=document.createElement("tr");const severityCell=document.createElement("td");const severityBadge=document.createElement("span");severityBadge.className="badge "+issue.severity;severityBadge.textContent=text.severities[issue.severity];severityCell.append(severityBadge);row.append(severityCell);const lifeCell=document.createElement("td");const lifeBadge=document.createElement("span");lifeBadge.className="badge "+issue.lifecycle;lifeBadge.textContent=text.lifecycle[issue.lifecycle]||issue.lifecycle;lifeCell.append(lifeBadge);row.append(lifeCell);cell(row,localized.rule+" · "+text.owners[issue.owner]);const pageCell=document.createElement("td");const link=document.createElement("a");link.href=issue.url;link.target="_blank";link.rel="noreferrer";link.textContent=issue.url;pageCell.append(link);row.append(pageCell);cell(row,localized.message);if(report.mode==="check"){const beforeCell=document.createElement("td");const code=document.createElement("code");code.textContent=valueText(issue.before);beforeCell.append(code);row.append(beforeCell)}const evidenceCell=document.createElement("td");const evidence=document.createElement("code");evidence.textContent=valueText(issue.after!==undefined?issue.after:issue.evidence);evidenceCell.append(evidence);row.append(evidenceCell);cell(row,localized.remediation,"remediation");const fingerprint=cell(row,issue.fingerprint,"fingerprint");const docs=document.createElement("a");docs.href=issue.documentationUrl;docs.target="_blank";docs.rel="noreferrer";docs.textContent=" "+text.docs;fingerprint.append(docs);tbody.append(row)}
for(const issue of visible){const localized=issueText(issue);const row=document.createElement("tr");const severityCell=document.createElement("td");const severityBadge=document.createElement("span");severityBadge.className="badge "+issue.severity;severityBadge.textContent=text.severities[issue.severity];severityCell.append(severityBadge);row.append(severityCell);const lifeCell=document.createElement("td");const lifeBadge=document.createElement("span");lifeBadge.className="badge "+issue.lifecycle;lifeBadge.textContent=text.lifecycle[issue.lifecycle]||issue.lifecycle;lifeCell.append(lifeBadge);row.append(lifeCell);cell(row,localized.rule+" · "+text.owners[issue.owner]);const pageCell=document.createElement("td");const link=document.createElement("a");link.href=issue.url;link.target="_blank";link.rel="noreferrer";link.textContent=issue.url;pageCell.append(link);row.append(pageCell);cell(row,localized.message);if(report.mode==="check"){const beforeCell=document.createElement("td");const code=document.createElement("code");code.textContent=valueText(issue.before);beforeCell.append(code);row.append(beforeCell)}const evidenceCell=document.createElement("td");const evidence=document.createElement("code");evidence.textContent=valueText(issue.after!==undefined?issue.after:issue.evidence);evidenceCell.append(evidence);row.append(evidenceCell);cell(row,localized.remediation,"remediation");const fingerprint=cell(row,issue.fingerprint,"fingerprint");const docs=document.createElement("a");docs.href=locale==="uk"?issue.documentationUrl.replace("/docs/rules.md#","/docs/rules.uk.md#"):issue.documentationUrl;docs.target="_blank";docs.rel="noreferrer";docs.textContent=" "+text.docs;fingerprint.append(docs);tbody.append(row)}
const issueLabel=locale==="uk"?text.issues:report.issues.length===1?text.issue:text.issues;resultCount.textContent=number(lastFiltered.length)+" "+text.of+" "+number(report.issues.length)+" "+issueLabel;pageInfo.textContent=text.page+" "+number(currentPage)+" "+text.of+" "+number(pageCount);previous.disabled=currentPage===1;next.disabled=currentPage===pageCount;pagination.hidden=lastFiltered.length===0||pageCount===1;tableWrap.hidden=lastFiltered.length===0;empty.hidden=lastFiltered.length!==0;if(lastFiltered.length===0){const reportIsClean=report.issues.length===0;emptyTitle.textContent=reportIsClean?text.empty.cleanTitle:text.empty.filteredTitle;emptyMessage.textContent=reportIsClean?text.empty.cleanMessage:text.empty.filteredMessage;clearFilters.hidden=reportIsClean}syncChartButtons()
}
for(const element of [search,severity,rule,template,owner,pageSize]){const update=()=>{currentPage=1;render()};element.addEventListener("input",update);element.addEventListener("change",update)}for(const tab of document.querySelectorAll("[data-tab]")){tab.addEventListener("click",()=>{setActiveTab(tab.dataset.tab);currentPage=1;render()})}previous.addEventListener("click",()=>{currentPage-=1;render()});next.addEventListener("click",()=>{currentPage+=1;render()});clearFilters.addEventListener("click",()=>{search.value="";severity.value="";rule.value="";template.value="";owner.value="";setActiveTab("");currentPage=1;render()});language.addEventListener("change",()=>{locale=language.value;applyLocale()});
Expand Down
4 changes: 2 additions & 2 deletions packages/action/action-dist/index.cjs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ Regression-only checks cover newly introduced indexing blocks, status changes,
metadata removal or edits, redirect changes, robots changes, and sharp sitemap
URL loss.

[Read the rule-by-rule documentation](docs/rules.md).
[Read the rule-by-rule documentation](https://github.com/MelnixDev/seo-crawl-audit/blob/main/docs/rules.md) ·
[Українська версія довідника правил](https://github.com/MelnixDev/seo-crawl-audit/blob/main/docs/rules.uk.md).

## Snapshots, lifecycle, and checkpoints

Expand Down
Loading