search-auditor 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +87 -38
- package/dist/cli.js +17 -9
- package/dist/collect.d.ts +5 -1
- package/dist/collect.js +186 -11
- package/dist/i18n/en.d.ts +462 -7
- package/dist/i18n/en.js +480 -25
- package/dist/i18n/index.d.ts +924 -14
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/ru.js +484 -24
- package/dist/index.d.ts +3 -145
- package/dist/index.js +14 -53
- package/dist/rules/can/can-1.d.ts +2 -0
- package/dist/rules/can/can-1.js +14 -0
- package/dist/rules/can/can-2.d.ts +2 -0
- package/dist/rules/can/can-2.js +20 -0
- package/dist/rules/can/can-3.d.ts +2 -0
- package/dist/rules/can/can-3.js +32 -0
- package/dist/rules/can/can-4.d.ts +2 -0
- package/dist/rules/can/can-4.js +18 -0
- package/dist/rules/can/can-5.d.ts +2 -0
- package/dist/rules/can/can-5.js +17 -0
- package/dist/rules/can/can-6.d.ts +2 -0
- package/dist/rules/can/can-6.js +22 -0
- package/dist/rules/can/can-7.d.ts +2 -0
- package/dist/rules/can/can-7.js +32 -0
- package/dist/rules/can/can-8.d.ts +2 -0
- package/dist/rules/can/can-8.js +23 -0
- package/dist/rules/can/can-9.d.ts +2 -0
- package/dist/rules/can/can-9.js +24 -0
- package/dist/rules/can/index.d.ts +7 -0
- package/dist/rules/can/index.js +10 -0
- package/dist/rules/can/shared.d.ts +11 -0
- package/dist/rules/can/shared.js +26 -0
- package/dist/rules/data/data-1.d.ts +2 -0
- package/dist/rules/data/data-1.js +16 -0
- package/dist/rules/data/data-2.d.ts +2 -0
- package/dist/rules/data/data-2.js +25 -0
- package/dist/rules/data/data-3.d.ts +2 -0
- package/dist/rules/data/data-3.js +29 -0
- package/dist/rules/data/data-4.d.ts +2 -0
- package/dist/rules/data/data-4.js +17 -0
- package/dist/rules/data/data-5.d.ts +2 -0
- package/dist/rules/data/data-5.js +28 -0
- package/dist/rules/data/data-6.d.ts +2 -0
- package/dist/rules/data/data-6.js +16 -0
- package/dist/rules/data/index.d.ts +8 -0
- package/dist/rules/data/index.js +7 -0
- package/dist/rules/data/shared.d.ts +22 -0
- package/dist/rules/data/shared.js +59 -0
- package/dist/rules/idx/idx-1.d.ts +2 -0
- package/dist/rules/idx/idx-1.js +15 -0
- package/dist/rules/idx/idx-2.d.ts +2 -0
- package/dist/rules/idx/idx-2.js +22 -0
- package/dist/rules/idx/idx-3.d.ts +2 -0
- package/dist/rules/idx/idx-3.js +35 -0
- package/dist/rules/idx/idx-4.d.ts +2 -0
- package/dist/rules/idx/idx-4.js +40 -0
- package/dist/rules/idx/idx-5.d.ts +2 -0
- package/dist/rules/idx/idx-5.js +24 -0
- package/dist/rules/idx/idx-6.d.ts +2 -0
- package/dist/rules/idx/idx-6.js +25 -0
- package/dist/rules/idx/idx-7.d.ts +2 -0
- package/dist/rules/idx/idx-7.js +29 -0
- package/dist/rules/idx/idx-8.d.ts +2 -0
- package/dist/rules/idx/idx-8.js +25 -0
- package/dist/rules/idx/idx-9.d.ts +2 -0
- package/dist/rules/idx/idx-9.js +22 -0
- package/dist/rules/idx/index.d.ts +7 -0
- package/dist/rules/idx/index.js +10 -0
- package/dist/rules/idx/shared.d.ts +18 -0
- package/dist/rules/idx/shared.js +18 -0
- package/dist/rules/img/img-1.d.ts +2 -0
- package/dist/rules/img/img-1.js +18 -0
- package/dist/rules/img/img-2.d.ts +2 -0
- package/dist/rules/img/img-2.js +20 -0
- package/dist/rules/img/img-3.d.ts +2 -0
- package/dist/rules/img/img-3.js +24 -0
- package/dist/rules/img/img-4.d.ts +2 -0
- package/dist/rules/img/img-4.js +19 -0
- package/dist/rules/img/img-5.d.ts +2 -0
- package/dist/rules/img/img-5.js +18 -0
- package/dist/rules/img/img-6.d.ts +2 -0
- package/dist/rules/img/img-6.js +20 -0
- package/dist/rules/img/img-7.d.ts +2 -0
- package/dist/rules/img/img-7.js +22 -0
- package/dist/rules/img/index.d.ts +7 -0
- package/dist/rules/img/index.js +8 -0
- package/dist/rules/img/shared.d.ts +26 -0
- package/dist/rules/img/shared.js +38 -0
- package/dist/rules/index.d.ts +1 -1
- package/dist/rules/index.js +23 -6
- package/dist/rules/lang/index.d.ts +8 -0
- package/dist/rules/lang/index.js +9 -0
- package/dist/rules/lang/lang-1.d.ts +2 -0
- package/dist/rules/lang/lang-1.js +17 -0
- package/dist/rules/lang/lang-2.d.ts +2 -0
- package/dist/rules/lang/lang-2.js +30 -0
- package/dist/rules/lang/lang-3.d.ts +2 -0
- package/dist/rules/lang/lang-3.js +22 -0
- package/dist/rules/lang/lang-4.d.ts +2 -0
- package/dist/rules/lang/lang-4.js +23 -0
- package/dist/rules/lang/lang-5.d.ts +2 -0
- package/dist/rules/lang/lang-5.js +33 -0
- package/dist/rules/lang/lang-6.d.ts +2 -0
- package/dist/rules/lang/lang-6.js +25 -0
- package/dist/rules/lang/lang-7.d.ts +2 -0
- package/dist/rules/lang/lang-7.js +25 -0
- package/dist/rules/lang/lang-8.d.ts +2 -0
- package/dist/rules/lang/lang-8.js +19 -0
- package/dist/rules/lang/shared.d.ts +7 -0
- package/dist/rules/lang/shared.js +5 -0
- package/dist/rules/lnk/index.d.ts +8 -0
- package/dist/rules/lnk/index.js +8 -0
- package/dist/rules/lnk/lnk-1.d.ts +2 -0
- package/dist/rules/lnk/lnk-1.js +22 -0
- package/dist/rules/lnk/lnk-2.d.ts +2 -0
- package/dist/rules/lnk/lnk-2.js +18 -0
- package/dist/rules/lnk/lnk-3.d.ts +2 -0
- package/dist/rules/lnk/lnk-3.js +32 -0
- package/dist/rules/lnk/lnk-4.d.ts +2 -0
- package/dist/rules/lnk/lnk-4.js +18 -0
- package/dist/rules/lnk/lnk-5.d.ts +2 -0
- package/dist/rules/lnk/lnk-5.js +22 -0
- package/dist/rules/lnk/lnk-6.d.ts +2 -0
- package/dist/rules/lnk/lnk-6.js +24 -0
- package/dist/rules/lnk/lnk-7.d.ts +2 -0
- package/dist/rules/lnk/lnk-7.js +26 -0
- package/dist/rules/lnk/shared.d.ts +20 -0
- package/dist/rules/lnk/shared.js +29 -0
- package/dist/rules/map/index.d.ts +7 -0
- package/dist/rules/map/index.js +9 -0
- package/dist/rules/map/map-1.d.ts +2 -0
- package/dist/rules/map/map-1.js +15 -0
- package/dist/rules/map/map-2.d.ts +2 -0
- package/dist/rules/map/map-2.js +26 -0
- package/dist/rules/map/map-3.d.ts +2 -0
- package/dist/rules/map/map-3.js +24 -0
- package/dist/rules/map/map-4.d.ts +2 -0
- package/dist/rules/map/map-4.js +34 -0
- package/dist/rules/map/map-5.d.ts +2 -0
- package/dist/rules/map/map-5.js +28 -0
- package/dist/rules/map/map-6.d.ts +2 -0
- package/dist/rules/map/map-6.js +22 -0
- package/dist/rules/map/map-7.d.ts +2 -0
- package/dist/rules/map/map-7.js +18 -0
- package/dist/rules/map/map-8.d.ts +2 -0
- package/dist/rules/map/map-8.js +31 -0
- package/dist/rules/map/shared.d.ts +14 -0
- package/dist/rules/map/shared.js +12 -0
- package/dist/rules/meta/index.d.ts +8 -0
- package/dist/rules/meta/index.js +10 -0
- package/dist/rules/meta/meta-1.d.ts +2 -0
- package/dist/rules/meta/meta-1.js +20 -0
- package/dist/rules/meta/meta-2.d.ts +2 -0
- package/dist/rules/meta/meta-2.js +19 -0
- package/dist/rules/meta/meta-3.d.ts +2 -0
- package/dist/rules/meta/meta-3.js +26 -0
- package/dist/rules/meta/meta-4.d.ts +2 -0
- package/dist/rules/meta/meta-4.js +22 -0
- package/dist/rules/meta/meta-5.d.ts +2 -0
- package/dist/rules/meta/meta-5.js +17 -0
- package/dist/rules/meta/meta-6.d.ts +2 -0
- package/dist/rules/meta/meta-6.js +18 -0
- package/dist/rules/meta/meta-7.d.ts +2 -0
- package/dist/rules/meta/meta-7.js +18 -0
- package/dist/rules/meta/meta-8.d.ts +2 -0
- package/dist/rules/meta/meta-8.js +17 -0
- package/dist/rules/meta/meta-9.d.ts +2 -0
- package/dist/rules/meta/meta-9.js +16 -0
- package/dist/rules/meta/shared.d.ts +15 -0
- package/dist/rules/meta/shared.js +9 -0
- package/dist/rules/sec/index.d.ts +8 -0
- package/dist/rules/sec/index.js +8 -0
- package/dist/rules/sec/sec-1.d.ts +2 -0
- package/dist/rules/sec/sec-1.js +21 -0
- package/dist/rules/sec/sec-2.d.ts +2 -0
- package/dist/rules/sec/sec-2.js +17 -0
- package/dist/rules/sec/sec-3.d.ts +2 -0
- package/dist/rules/sec/sec-3.js +18 -0
- package/dist/rules/sec/sec-4.d.ts +2 -0
- package/dist/rules/sec/sec-4.js +23 -0
- package/dist/rules/sec/sec-5.d.ts +2 -0
- package/dist/rules/sec/sec-5.js +23 -0
- package/dist/rules/sec/sec-6.d.ts +2 -0
- package/dist/rules/sec/sec-6.js +32 -0
- package/dist/rules/sec/sec-7.d.ts +2 -0
- package/dist/rules/sec/sec-7.js +26 -0
- package/dist/rules/sec/shared.d.ts +10 -0
- package/dist/rules/sec/shared.js +8 -0
- package/dist/rules/str/index.d.ts +8 -0
- package/dist/rules/str/index.js +11 -0
- package/dist/rules/str/shared.d.ts +14 -0
- package/dist/rules/str/shared.js +16 -0
- package/dist/rules/str/str-1.d.ts +2 -0
- package/dist/rules/str/str-1.js +17 -0
- package/dist/rules/str/str-10.d.ts +2 -0
- package/dist/rules/str/str-10.js +15 -0
- package/dist/rules/str/str-2.d.ts +2 -0
- package/dist/rules/str/str-2.js +19 -0
- package/dist/rules/str/str-3.d.ts +2 -0
- package/dist/rules/str/str-3.js +14 -0
- package/dist/rules/str/str-4.d.ts +2 -0
- package/dist/rules/str/str-4.js +14 -0
- package/dist/rules/str/str-5.d.ts +2 -0
- package/dist/rules/str/str-5.js +19 -0
- package/dist/rules/str/str-6.d.ts +2 -0
- package/dist/rules/str/str-6.js +22 -0
- package/dist/rules/str/str-7.d.ts +2 -0
- package/dist/rules/str/str-7.js +18 -0
- package/dist/rules/str/str-8.d.ts +2 -0
- package/dist/rules/str/str-8.js +24 -0
- package/dist/rules/str/str-9.d.ts +2 -0
- package/dist/rules/str/str-9.js +14 -0
- package/dist/rules/util.d.ts +1 -1
- package/dist/trace.d.ts +20 -0
- package/dist/trace.js +77 -0
- package/dist/types.d.ts +86 -6
- package/package.json +4 -9
- package/dist/rules/links.d.ts +0 -2
- package/dist/rules/links.js +0 -95
- package/dist/rules/page.d.ts +0 -2
- package/dist/rules/page.js +0 -145
- package/dist/rules/site.d.ts +0 -2
- package/dist/rules/site.js +0 -177
package/dist/i18n/ru.js
CHANGED
|
@@ -1,146 +1,605 @@
|
|
|
1
|
+
/** «символ», «символа», «символов» for a count. */
|
|
2
|
+
const chars = (n) => {
|
|
3
|
+
const d = n % 10, h = n % 100;
|
|
4
|
+
return d === 1 && h !== 11 ? "символ" : d >= 2 && d <= 4 && (h < 12 || h > 14) ? "символа" : "символов";
|
|
5
|
+
};
|
|
1
6
|
export const ru = {
|
|
2
7
|
report: {
|
|
3
|
-
title: (site) =>
|
|
4
|
-
clean: "Исправлять нечего: поисковики
|
|
5
|
-
footer: (pages, date) => `Проверено страниц: ${pages}, ${date}. После каждого деплоя проверьте и вручную: Google Search Console, Bing Webmaster Tools
|
|
6
|
-
classic: "Классический поиск",
|
|
7
|
-
ai: "AI-поиск",
|
|
8
|
+
title: (site) => `SEO-аудит: ${site}`,
|
|
9
|
+
clean: "Исправлять нечего: поисковики могут найти эти страницы, проиндексировать их и показать в выдаче.",
|
|
10
|
+
footer: (pages, date) => `Проверено страниц: ${pages}, ${date}. После каждого деплоя проверьте и вручную: Google Search Console, Bing Webmaster Tools, Яндекс Вебмастер и живой CDN реальными краулерами.`,
|
|
8
11
|
},
|
|
9
12
|
groups: {
|
|
10
13
|
IDX: "Индексация",
|
|
11
|
-
CAN: "Canonical",
|
|
12
|
-
MAP: "Sitemap",
|
|
13
|
-
META: "
|
|
14
|
-
STR: "Структура
|
|
15
|
-
LNK: "
|
|
16
|
-
IMG: "
|
|
17
|
-
DATA: "Структурированные
|
|
18
|
-
LANG: "Языковые
|
|
19
|
-
SEC: "HTTPS",
|
|
14
|
+
CAN: "Canonical: основной адрес страницы",
|
|
15
|
+
MAP: "Sitemap: карта сайта для поисковиков",
|
|
16
|
+
META: "Мета-теги: как страница выглядит в выдаче",
|
|
17
|
+
STR: "Структура страницы: заголовки, язык, мобильная версия",
|
|
18
|
+
LNK: "Ссылки: как страницы связаны между собой",
|
|
19
|
+
IMG: "Картинки: поиск по картинкам и скорость загрузки",
|
|
20
|
+
DATA: "Структурированные данные: расширенные сниппеты",
|
|
21
|
+
LANG: "Языковые версии: hreflang для многоязычных сайтов",
|
|
22
|
+
SEC: "Безопасность и HTTPS",
|
|
20
23
|
},
|
|
21
24
|
rules: {
|
|
22
25
|
"IDX-1": {
|
|
23
26
|
title: "Страницы отвечают 200",
|
|
27
|
+
todo: "Почините страницы с ошибками или уберите их из sitemap",
|
|
24
28
|
fix: "Почините страницы, которые отвечают ошибкой или не отвечают, либо уберите их из sitemap и внутренних ссылок. Поисковики выкидывают страницы, которые стабильно не открываются.",
|
|
25
29
|
impact: "Часть страниц отвечает ошибкой, и поисковики убирают их из выдачи.",
|
|
26
30
|
},
|
|
27
31
|
"IDX-2": {
|
|
28
32
|
title: "Несуществующие страницы отвечают 404",
|
|
33
|
+
todo: "Отдавайте 404 на несуществующие адреса",
|
|
29
34
|
fix: "Отдавайте 404 (или 410) на несуществующие адреса и не показывайте страницу «не найдено» с кодом 200. Иначе поисковики индексируют пустые страницы и тратят на них обход.",
|
|
30
35
|
impact: "Пустые страницы «не найдено» выглядят как настоящие: в индекс попадает мусор, а сайт обходят хуже.",
|
|
31
36
|
},
|
|
37
|
+
"IDX-3": {
|
|
38
|
+
title: "robots.txt отвечает без ошибок",
|
|
39
|
+
todo: "Сделайте, чтобы robots.txt отвечал 200 и текстовыми правилами",
|
|
40
|
+
fix: "Отдавайте robots.txt текстом с кодом 200 или 404, если его нет. При 5xx, 429 или таймауте Google считает весь сайт закрытым, пока файл не ответит; HTML вместо правил, непонятные строки и всё после 500 КБ поисковики пропускают.",
|
|
41
|
+
impact: "Поисковики могут перестать обходить сайт или проигнорировать его правила.",
|
|
42
|
+
},
|
|
43
|
+
"IDX-4": {
|
|
44
|
+
title: "robots.txt не закрывает страницы от поисковиков",
|
|
45
|
+
todo: "Откройте страницы сайта в robots.txt",
|
|
46
|
+
fix: "Уберите Disallow, который закрывает эти страницы для Googlebot, Bingbot и Яндекса, или уберите страницы из sitemap, если они должны оставаться закрытыми. Страница может быть либо в sitemap, либо закрыта, но не то и другое сразу; сайт за Disallow: / не индексируется вовсе.",
|
|
47
|
+
impact: "Закрытые страницы поисковик не читает и не показывает в выдаче.",
|
|
48
|
+
},
|
|
49
|
+
"IDX-5": {
|
|
50
|
+
title: "У сайта одна версия",
|
|
51
|
+
todo: "Настройте редирект с www и второго варианта слеша на основной адрес",
|
|
52
|
+
fix: "Выберите один домен (с www. или без) и одну форму адреса (со слешем в конце или без) и перенаправляйте вторую версию с кодом 301. Если обе должны отвечать, укажите в canonical обеих основной адрес.",
|
|
53
|
+
impact: "Поисковики видят две копии сайта и делят его вес между ними.",
|
|
54
|
+
},
|
|
55
|
+
"IDX-6": {
|
|
56
|
+
title: "Редиректы ведут в один шаг",
|
|
57
|
+
todo: "Ведите редиректы сразу на конечный адрес",
|
|
58
|
+
fix: "Пусть каждый редирект ведёт на конечный адрес одним 301, а ссылки и sitemap указывают сразу на него. Цепочки замедляют обход, а петля делает страницу недоступной.",
|
|
59
|
+
impact: "Каждый лишний редирект замедляет обход, а петля делает страницу недоступной.",
|
|
60
|
+
},
|
|
61
|
+
"IDX-7": {
|
|
62
|
+
title: "noindex не закрывает главную и не противоречит sitemap и canonical",
|
|
63
|
+
todo: "Решите для каждой страницы: в индексе она или закрыта",
|
|
64
|
+
fix: "Страница из sitemap должна быть открыта для индексации; страница с noindex не должна быть в sitemap и не должна быть целью canonical. Не сочетайте noindex с canonical на другую страницу.",
|
|
65
|
+
impact: "Поисковики получают противоположные сигналы и могут выкинуть не ту страницу.",
|
|
66
|
+
},
|
|
67
|
+
"IDX-8": {
|
|
68
|
+
title: "robots.txt не закрывает CSS и JavaScript",
|
|
69
|
+
todo: "Откройте поисковикам CSS и JavaScript в robots.txt",
|
|
70
|
+
fix: "Разрешите Googlebot загружать стили и скрипты, без которых страница не отрисуется. Google рендерит страницу перед ранжированием и без них видит сломанную вёрстку.",
|
|
71
|
+
impact: "Google видит сломанную страницу и ранжирует её ниже, особенно в мобильной выдаче.",
|
|
72
|
+
},
|
|
73
|
+
"IDX-9": {
|
|
74
|
+
title: "Сервер отвечает быстро",
|
|
75
|
+
todo: "Ускорьте ответ сервера",
|
|
76
|
+
fix: "Добивайтесь первого байта быстрее 0,8 с: кэшируйте страницы на CDN или отдавайте статический HTML, проверьте медленные запросы к базе. Медленные сайты Google обходит реже.",
|
|
77
|
+
impact: "Медленный сервер заставляет поисковики обходить меньше страниц, и новые появляются в выдаче позже.",
|
|
78
|
+
},
|
|
32
79
|
"CAN-1": {
|
|
33
80
|
title: "У каждой страницы есть canonical",
|
|
81
|
+
todo: "Добавьте canonical на каждую страницу",
|
|
34
82
|
fix: 'Добавьте на каждую страницу `<link rel="canonical" href="https://example.com/page">` с её собственным адресом, чтобы дубли (параметры, слеш в конце) указывали на одну версию.',
|
|
35
83
|
impact: "Без canonical копии страницы с параметрами конкурируют друг с другом в поиске.",
|
|
36
84
|
},
|
|
37
85
|
"CAN-2": {
|
|
38
86
|
title: "Canonical — абсолютный адрес на этом сайте",
|
|
87
|
+
todo: "Сделайте canonical полным адресом на своём домене",
|
|
39
88
|
fix: "Указывайте в canonical полный адрес с https и правильным доменом. Относительный или чужой canonical велит поисковику ранжировать другую страницу.",
|
|
40
89
|
impact: "Canonical указывает не туда, и поисковик может показывать другую страницу или никакую.",
|
|
41
90
|
},
|
|
42
91
|
"CAN-3": {
|
|
43
92
|
title: "Canonical ведёт на рабочую страницу",
|
|
93
|
+
todo: "Направьте canonical на рабочий конечный адрес",
|
|
44
94
|
fix: "Ставьте в canonical конечный адрес, который отвечает 200, а не редирект и не ошибку.",
|
|
45
95
|
impact: "Canonical ведёт на сломанную страницу или редирект, и сама страница может пропасть из поиска.",
|
|
46
96
|
},
|
|
97
|
+
"CAN-4": {
|
|
98
|
+
title: "Один canonical на странице",
|
|
99
|
+
todo: "Оставьте на странице один canonical",
|
|
100
|
+
fix: "Оставьте один `<link rel=\"canonical\">`. Когда их два и они разные (часто один от CMS, второй от SEO-плагина), Google не учитывает оба.",
|
|
101
|
+
impact: "При двух разных canonical поисковики не учитывают ни один и выбирают основной адрес сами.",
|
|
102
|
+
},
|
|
103
|
+
"CAN-5": {
|
|
104
|
+
title: "Canonical стоит в <head>",
|
|
105
|
+
todo: "Перенесите canonical в <head>",
|
|
106
|
+
fix: "Ставьте `<link rel=\"canonical\">` в `<head>`. Canonical в `<body>`, куда его часто выталкивают скрипты или незакрытый тег, Google не учитывает.",
|
|
107
|
+
impact: "Canonical вне <head> не учитывается, как будто его нет.",
|
|
108
|
+
},
|
|
109
|
+
"CAN-6": {
|
|
110
|
+
title: "Canonical в HTML и в HTTP-заголовке совпадают",
|
|
111
|
+
todo: "Сделайте canonical одинаковым в HTML и в заголовке Link",
|
|
112
|
+
fix: "Указывайте один и тот же адрес в `<link rel=\"canonical\">` и в заголовке `Link: <…>; rel=\"canonical\"` или оставьте что-то одно. При противоречии Google выбирает сам.",
|
|
113
|
+
impact: "Поисковики получают два разных основных адреса и могут показывать не тот.",
|
|
114
|
+
},
|
|
115
|
+
"CAN-7": {
|
|
116
|
+
title: "Canonical ведёт сразу на основную страницу",
|
|
117
|
+
todo: "Ведите canonical сразу на конечную основную страницу",
|
|
118
|
+
fix: "Если страница A указывает canonical на B, то B должна указывать на себя, а не на C. Указывайте в canonical каждой копии сразу основную страницу.",
|
|
119
|
+
impact: "Поисковики могут не пройти цепочку до конца и показывать копию.",
|
|
120
|
+
},
|
|
121
|
+
"CAN-8": {
|
|
122
|
+
title: "Адрес с UTM-метками указывает canonical на чистый адрес",
|
|
123
|
+
todo: "Формируйте canonical без параметров адреса",
|
|
124
|
+
fix: "Собирайте canonical из адреса самой страницы, а не из запрошенного: с `?utm_source=…` canonical должен оставаться чистым адресом. Иначе каждая ссылка из рекламы создаёт отдельную страницу.",
|
|
125
|
+
impact: "Каждая ссылка с UTM-метками становится отдельной страницей в индексе, и вес расходится по копиям.",
|
|
126
|
+
},
|
|
127
|
+
"CAN-9": {
|
|
128
|
+
title: "Языковые версии не указывают canonical друг на друга",
|
|
129
|
+
todo: "Укажите в canonical каждой языковой версии её саму",
|
|
130
|
+
fix: "Каждый перевод указывает canonical на себя и ссылается на остальные через hreflang. Canonical на другой язык говорит поисковику, что страница — копия, и она пропадает из выдачи на своём языке.",
|
|
131
|
+
impact: "Перевод пропадает из выдачи, и люди, которые ищут на его языке, получают другую версию.",
|
|
132
|
+
},
|
|
47
133
|
"MAP-1": {
|
|
48
134
|
title: "Есть sitemap, и robots.txt на него указывает",
|
|
135
|
+
todo: "Добавьте sitemap.xml и укажите его в robots.txt",
|
|
49
136
|
fix: "Отдавайте /sitemap.xml со всеми страницами, которые должны быть в поиске, и добавьте в robots.txt строку `Sitemap: https://example.com/sitemap.xml`.",
|
|
50
137
|
impact: "Без sitemap поисковики позже находят новые и глубокие страницы.",
|
|
51
138
|
},
|
|
52
139
|
"MAP-2": {
|
|
53
140
|
title: "Адреса в sitemap не редиректят",
|
|
141
|
+
todo: "Замените в sitemap адреса с редиректом на конечные",
|
|
54
142
|
fix: "Указывайте в sitemap конечные адреса, которые отвечают 200 без редиректов.",
|
|
55
143
|
impact: "Sitemap гоняет поисковики через редиректы, и индексация замедляется.",
|
|
56
144
|
},
|
|
57
145
|
"MAP-3": {
|
|
58
146
|
title: "Страницы из sitemap канонические",
|
|
147
|
+
todo: "Уберите из sitemap неканонические страницы",
|
|
59
148
|
fix: "Держите в sitemap только канонические страницы. Страница, чей canonical указывает на другую, — дубль, ей там не место.",
|
|
60
149
|
impact: "В sitemap есть дубли, и поисковик получает противоречивые сигналы, какую страницу показывать.",
|
|
61
150
|
},
|
|
151
|
+
"MAP-4": {
|
|
152
|
+
title: "Файл sitemap корректный",
|
|
153
|
+
todo: "Исправьте файл sitemap",
|
|
154
|
+
fix: "Отдавайте sitemap как XML с кодом 200: `<urlset>` с записями `<url><loc>…</loc></url>` или `<sitemapindex>` со списком таких файлов. Экранируйте & как `&` внутри адресов, держите в файле не больше 50 000 адресов и 50 МБ, и пусть каждый файл из индекса отвечает.",
|
|
155
|
+
impact: "Поисковики не могут прочитать sitemap или выбрасывают его и находят новые страницы только случайно.",
|
|
156
|
+
},
|
|
157
|
+
"MAP-5": {
|
|
158
|
+
title: "Адреса в sitemap полные и с этого сайта",
|
|
159
|
+
todo: "Пишите в sitemap полные адреса этого сайта",
|
|
160
|
+
fix: "Указывайте полные адреса с протоколом и доменом этого сайта: `https://example.com/page`, а не `/page` и не другой домен. Остальное поисковики пропускают.",
|
|
161
|
+
impact: "Поисковики пропускают адреса, которые не могут использовать, и эти страницы находятся позже или никогда.",
|
|
162
|
+
},
|
|
163
|
+
"MAP-6": {
|
|
164
|
+
title: "Страницы, на которые ссылается сайт, есть в sitemap",
|
|
165
|
+
todo: "Добавьте страницы со ссылками на них в sitemap",
|
|
166
|
+
fix: "Указывайте в sitemap все страницы, которые должны быть в поиске, а не только те, что знает генератор: страницы со ссылками на сайте, но без записи в sitemap, находятся позже. Если страница не должна попасть в поиск, закройте её noindex.",
|
|
167
|
+
impact: "Поисковики находят эти страницы позже и только по ссылкам.",
|
|
168
|
+
},
|
|
169
|
+
"MAP-7": {
|
|
170
|
+
title: "Все адреса sitemap отвечают",
|
|
171
|
+
todo: "Уберите из sitemap мёртвые адреса",
|
|
172
|
+
fix: "Оставляйте в sitemap только страницы, которые отвечают 200: удалённые уберите или перенаправьте, а sitemap собирайте из опубликованного.",
|
|
173
|
+
impact: "Sitemap с мёртвыми страницами приучает поисковики меньше ему доверять и тратит их обход впустую.",
|
|
174
|
+
},
|
|
175
|
+
"MAP-8": {
|
|
176
|
+
title: "lastmod в sitemap честный",
|
|
177
|
+
todo: "Ставьте в lastmod настоящую дату правки страницы",
|
|
178
|
+
fix: "Пишите lastmod в формате W3C (`2026-11-09` или `2026-11-09T10:00:00+03:00`) по дате, когда содержимое страницы действительно менялось. Не в будущем и не время сборки на всех адресах сразу: такие даты Google перестаёт учитывать.",
|
|
179
|
+
impact: "Поисковики не учитывают lastmod, и обновлённые страницы переобходятся позже.",
|
|
180
|
+
},
|
|
62
181
|
"META-1": {
|
|
63
182
|
title: "У каждой страницы есть title нужной длины",
|
|
183
|
+
todo: "Напишите title на 10–60 символов",
|
|
64
184
|
fix: "Дайте каждой странице уникальный <title> примерно на 10–60 символов, главный запрос — в начале. Длинные заголовки обрезаются в выдаче.",
|
|
65
185
|
impact: "Без заголовка или с обрезанным заголовком страница хуже кликается и хуже соответствует запросу.",
|
|
66
186
|
},
|
|
67
187
|
"META-2": {
|
|
68
|
-
title: "У каждой страницы есть description",
|
|
188
|
+
title: "У каждой страницы есть description нужной длины",
|
|
189
|
+
todo: "Напишите description на 50–160 символов",
|
|
69
190
|
fix: 'Добавьте `<meta name="description">` примерно на 50–160 символов: что на странице и зачем её открывать.',
|
|
70
|
-
impact: "Без описания поисковик берёт случайный кусок текста, и кликают реже.",
|
|
191
|
+
impact: "Без описания поисковик берёт случайный кусок текста, длинное обрезает на полуслове, и кликают реже.",
|
|
71
192
|
},
|
|
72
193
|
"META-3": {
|
|
73
194
|
title: "Title и description не повторяются на разных страницах",
|
|
195
|
+
todo: "Сделайте title и description своими на каждой странице",
|
|
74
196
|
fix: "Напишите для каждой страницы свой заголовок и описание. Одинаковые делают страницы похожими на дубли.",
|
|
75
197
|
impact: "Страницы с одинаковым заголовком конкурируют между собой, и в выдаче оказывается не та.",
|
|
76
198
|
},
|
|
199
|
+
"META-4": {
|
|
200
|
+
title: "Один title и одно description на странице",
|
|
201
|
+
todo: "Оставьте в <head> один title и одно description",
|
|
202
|
+
fix: "Оставьте в `<head>` один `<title>` и одно `<meta name=\"description\">`. Два обычно появляются, когда их задают и общий шаблон, и сама страница; поисковик тогда берёт любое.",
|
|
203
|
+
impact: "Поисковик берёт любой из заголовков или описаний, не обязательно тот, что вы написали.",
|
|
204
|
+
},
|
|
205
|
+
"META-5": {
|
|
206
|
+
title: "Title говорит о странице, а не о шаблоне",
|
|
207
|
+
todo: "Напишите title о том, чему посвящена страница",
|
|
208
|
+
fix: "Замените «Главная», «Без названия», «React App» и подобное на заголовок об этой странице: что на ней и для кого, главный запрос — в начале.",
|
|
209
|
+
impact: "Заголовок вроде «Главная» ничего не говорит в выдаче: страницу мало кликают, и она ни по чему не ранжируется.",
|
|
210
|
+
},
|
|
211
|
+
"META-6": {
|
|
212
|
+
title: "Description дополняет title",
|
|
213
|
+
todo: "Напишите description, который дополняет title",
|
|
214
|
+
fix: "Пишите в description то, чего нет в заголовке: что на странице, для кого и зачем её открывать. Повтор заголовка тратит сниппет впустую.",
|
|
215
|
+
impact: "Сниппет повторяет заголовок и не даёт причины кликнуть.",
|
|
216
|
+
},
|
|
217
|
+
"META-7": {
|
|
218
|
+
title: "Страница объявляет кодировку",
|
|
219
|
+
todo: "Объявите кодировку: <meta charset=\"utf-8\">",
|
|
220
|
+
fix: "Поставьте `<meta charset=\"utf-8\">` первым в `<head>` или отдавайте `Content-Type: text/html; charset=utf-8`. Без этого кириллица может превратиться в кракозябры в браузере и в сниппете.",
|
|
221
|
+
impact: "Текст может отображаться кракозябрами в браузере и в выдаче.",
|
|
222
|
+
},
|
|
223
|
+
"META-8": {
|
|
224
|
+
title: "Сниппет не запрещён",
|
|
225
|
+
todo: "Уберите nosnippet и max-snippet:0",
|
|
226
|
+
fix: "Уберите `nosnippet` и `max-snippet:0` из meta robots и X-Robots-Tag. Они убирают описание под результатом, а такие страницы Google не берёт в AI-обзоры.",
|
|
227
|
+
impact: "У результата нет описания, и Google не берёт страницу в AI-ответы.",
|
|
228
|
+
},
|
|
229
|
+
"META-9": {
|
|
230
|
+
title: "У сайта есть favicon",
|
|
231
|
+
todo: "Добавьте favicon",
|
|
232
|
+
fix: "Укажите `<link rel=\"icon\" href=\"/favicon.svg\">` (или отдавайте /favicon.ico) размером от 48×48. Google показывает его рядом с каждым результатом сайта.",
|
|
233
|
+
impact: "В выдаче вместо вашей иконки пустой глобус, и результат заметен хуже.",
|
|
234
|
+
},
|
|
77
235
|
"STR-1": {
|
|
78
236
|
title: "Один H1 на странице",
|
|
237
|
+
todo: "Оставьте на странице один H1",
|
|
79
238
|
fix: "Дайте каждой странице ровно один H1 о том, чему она посвящена; разделы — H2–H6.",
|
|
80
239
|
impact: "Без одного ясного H1 поисковик хуже понимает тему страницы.",
|
|
81
240
|
},
|
|
82
241
|
"STR-2": {
|
|
83
242
|
title: "Уровни заголовков идут по порядку",
|
|
243
|
+
todo: "Выстройте заголовки по уровням без пропусков",
|
|
84
244
|
fix: "Спускайтесь на один уровень: H1, затем H2, затем H3. Размер задавайте в CSS, а не выбором уровня.",
|
|
85
245
|
impact: "Сломанная структура заголовков мешает поисковикам и экранным дикторам понять страницу.",
|
|
86
246
|
},
|
|
87
247
|
"STR-3": {
|
|
88
248
|
title: "Страница объявляет свой язык",
|
|
249
|
+
todo: "Укажите язык в <html lang>",
|
|
89
250
|
fix: 'Укажите на каждой странице `<html lang="ru">` с её настоящим языком.',
|
|
90
251
|
impact: "Без языка поисковики и браузеры могут показать страницу не той аудитории или предложить перевести.",
|
|
91
252
|
},
|
|
92
253
|
"STR-4": {
|
|
93
254
|
title: "Страницы настроены для телефонов",
|
|
255
|
+
todo: "Добавьте meta viewport",
|
|
94
256
|
fix: 'Добавьте `<meta name="viewport" content="width=device-width, initial-scale=1">`. Google ранжирует по мобильной версии страницы.',
|
|
95
257
|
impact: "Без viewport страница на телефоне выглядит как уменьшенная десктопная, и мобильная выдача страдает.",
|
|
96
258
|
},
|
|
259
|
+
"STR-5": {
|
|
260
|
+
title: "Заголовки не пустые",
|
|
261
|
+
todo: "Добавьте текст в H1 и в пустые заголовки",
|
|
262
|
+
fix: "Дайте H1 настоящий текст, а не только картинку-логотип: alt картинки не считается текстом заголовка. Пустые H2–H6 уберите или заполните; их часто оставляет шаблон или используют для отступов.",
|
|
263
|
+
impact: "H1 без текста читается как отсутствие H1, и поисковик угадывает тему страницы.",
|
|
264
|
+
},
|
|
265
|
+
"STR-6": {
|
|
266
|
+
title: "Title и H1 об одном и том же",
|
|
267
|
+
todo: "Сделайте, чтобы title и H1 называли одну тему",
|
|
268
|
+
fix: "Пусть в title и H1 будут общие главные слова страницы. Когда они расходятся, Google часто переписывает заголовок в выдаче по H1 или по тексту.",
|
|
269
|
+
impact: "Google может показать в выдаче свой заголовок вместо вашего.",
|
|
270
|
+
},
|
|
271
|
+
"STR-7": {
|
|
272
|
+
title: "Страницу можно увеличить на телефоне",
|
|
273
|
+
todo: "Разрешите масштабирование во viewport",
|
|
274
|
+
fix: "Уберите `user-scalable=no` и оставьте `maximum-scale` не меньше 2 (или уберите его). Люди со слабым зрением увеличивают текст, а Lighthouse считает запрет масштаба ошибкой доступности.",
|
|
275
|
+
impact: "Часть посетителей не может прочитать страницу на телефоне, и оценка доступности падает.",
|
|
276
|
+
},
|
|
277
|
+
"STR-8": {
|
|
278
|
+
title: "<html lang> совпадает с языком текста",
|
|
279
|
+
todo: "Укажите в <html lang> настоящий язык страницы",
|
|
280
|
+
fix: "Указывайте язык, на котором написана страница: `<html lang=\"ru\">` для русской. С неверным языком страница уходит не той аудитории, а браузер предлагает её перевести.",
|
|
281
|
+
impact: "Поисковики могут показывать страницу не той аудитории, а браузер предлагает её перевести.",
|
|
282
|
+
},
|
|
283
|
+
"STR-9": {
|
|
284
|
+
title: "Основное содержимое в <main>",
|
|
285
|
+
todo: "Оберните основное содержимое в <main>",
|
|
286
|
+
fix: "Поместите содержимое страницы в `<main>`, отдельно от шапки, меню и подвала. Поисковикам, AI-поиску и экранным дикторам так проще найти главный текст.",
|
|
287
|
+
impact: "Краулеры и экранные дикторы хуже отличают содержимое от меню.",
|
|
288
|
+
},
|
|
289
|
+
"STR-10": {
|
|
290
|
+
title: "Страница начинается с <!doctype html>",
|
|
291
|
+
todo: "Добавьте <!doctype html> первой строкой",
|
|
292
|
+
fix: "Начинайте каждую страницу с `<!doctype html>`. Без него браузеры включают режим совместимости (quirks mode), и вёрстка может ломаться по-разному в разных браузерах.",
|
|
293
|
+
impact: "Страница может выглядеть по-разному в разных браузерах, а в некоторых — сломанной.",
|
|
294
|
+
},
|
|
97
295
|
"LNK-1": {
|
|
98
296
|
title: "Внутренние ссылки работают",
|
|
297
|
+
todo: "Почините битые внутренние ссылки",
|
|
99
298
|
fix: "Почините или уберите ссылки, которые ведут на ошибки. Битые ссылки тратят обход и теряют вес, который могли бы передать.",
|
|
100
299
|
impact: "Ссылки ведут на сломанные страницы: посетители уходят, а поисковики обходят сайт хуже.",
|
|
101
300
|
},
|
|
102
301
|
"LNK-2": {
|
|
103
302
|
title: "Внутренние ссылки не идут через редиректы",
|
|
303
|
+
todo: "Ведите ссылки сразу на конечный адрес",
|
|
104
304
|
fix: "Ссылайтесь сразу на конечный адрес (с правильным слешем, доменом и https), а не на тот, что редиректит.",
|
|
105
305
|
impact: "Каждый редирект в ссылке замедляет обход и немного снижает её вес.",
|
|
106
306
|
},
|
|
307
|
+
"LNK-3": {
|
|
308
|
+
title: "На каждую страницу ведёт ссылка",
|
|
309
|
+
todo: "Поставьте ссылки на страницы-сироты со связанных страниц",
|
|
310
|
+
fix: "Добавьте ссылки на эти страницы из меню, списков или похожих страниц. Страницу, которую знает только sitemap, обходят редко, и она не получает вес от остального сайта. Если она не нужна, уберите её из sitemap.",
|
|
311
|
+
impact: "Страницы, на которые никто не ссылается, обходят редко, и они слабо ранжируются.",
|
|
312
|
+
},
|
|
313
|
+
"LNK-4": {
|
|
314
|
+
title: "По ссылкам может перейти краулер",
|
|
315
|
+
todo: "Дайте каждой ссылке настоящий href",
|
|
316
|
+
fix: "Для переходов используйте `<a href=\"/page\">`, а не `<a>` без href, `href=\"#\"` или `javascript:` с обработчиком клика. Краулеры переходят только по href; для действий, которые не ведут на страницу, используйте `<button>`.",
|
|
317
|
+
impact: "Краулеры не могут перейти по этим ссылкам, и страницы за ними находятся позже или никогда.",
|
|
318
|
+
},
|
|
319
|
+
"LNK-5": {
|
|
320
|
+
title: "На внутренних ссылках нет nofollow",
|
|
321
|
+
todo: "Уберите rel=\"nofollow\" со ссылок на свои страницы",
|
|
322
|
+
fix: "Оставьте `nofollow` для платных ссылок и ссылок, за которые вы не ручаетесь. На ссылках на свои страницы он мешает весу переходить внутри сайта и выключает эти страницы из обхода.",
|
|
323
|
+
impact: "Ваши страницы получают меньше веса, и их реже обходят.",
|
|
324
|
+
},
|
|
325
|
+
"LNK-6": {
|
|
326
|
+
title: "Текст ссылки говорит, куда она ведёт",
|
|
327
|
+
todo: "Пишите в тексте ссылки, что на странице, куда она ведёт",
|
|
328
|
+
fix: "Замените «подробнее», «здесь» и пустые ссылки-картинки на текст о целевой странице: «Цены для команд», «Как мы тестируем». По тексту ссылок поисковики понимают, о чём целевая страница, а экранные дикторы читают его отдельно от текста вокруг.",
|
|
329
|
+
impact: "Поисковики меньше узнают о целевых страницах, а пользователи экранных дикторов слышат «ссылка, ссылка».",
|
|
330
|
+
},
|
|
331
|
+
"LNK-7": {
|
|
332
|
+
title: "Ссылки на другие сайты работают",
|
|
333
|
+
todo: "Исправьте или уберите мёртвые ссылки на другие сайты",
|
|
334
|
+
fix: "Обновите ссылки на удалённые страницы и сайты, которых больше нет, или дайте ссылку на копию в архиве. Мёртвые внешние ссылки делают страницу заброшенной на вид.",
|
|
335
|
+
impact: "Посетители упираются в тупик, а страница выглядит заброшенной для людей и поисковиков.",
|
|
336
|
+
},
|
|
107
337
|
"IMG-1": {
|
|
108
338
|
title: "У картинок есть alt",
|
|
339
|
+
todo: "Добавьте картинкам alt",
|
|
109
340
|
fix: 'Добавьте `alt` с тем, что изображено; для декоративных картинок — `alt=""`. От этого зависят поиск по картинкам и экранные дикторы.',
|
|
110
341
|
impact: "Картинки без alt не видны поиску по картинкам и людям с экранными дикторами.",
|
|
111
342
|
},
|
|
343
|
+
"IMG-2": {
|
|
344
|
+
title: "У картинок есть width и height",
|
|
345
|
+
todo: "Укажите картинкам width и height",
|
|
346
|
+
fix: "Задайте каждому `<img>` атрибуты `width` и `height` (настоящий размер; CSS всё равно может его менять). Браузер заранее оставит место, и вёрстка не будет прыгать при загрузке (CLS, часть Core Web Vitals).",
|
|
347
|
+
impact: "Страница прыгает при загрузке картинок: люди промахиваются мимо кнопок, и Core Web Vitals ухудшаются.",
|
|
348
|
+
},
|
|
349
|
+
"IMG-3": {
|
|
350
|
+
title: "Ленивая загрузка там, где она помогает",
|
|
351
|
+
todo: "Загружайте первую картинку сразу, а остальные лениво",
|
|
352
|
+
fix: "Не ставьте `loading=\"lazy\"` на первую картинку, обычно самую крупную на первом экране (LCP); ставьте его картинкам ниже на длинной странице, чтобы они не мешали загрузке видимого.",
|
|
353
|
+
impact: "Главная картинка появляется позже, или первый экран ждёт картинки, которых ещё никто не видит.",
|
|
354
|
+
},
|
|
355
|
+
"IMG-4": {
|
|
356
|
+
title: "Фотографии в современных форматах",
|
|
357
|
+
todo: "Отдавайте фотографии в WebP или AVIF",
|
|
358
|
+
fix: "Отдавайте WebP или AVIF: они на 25–50% легче JPEG и PNG при том же качестве. Через `<picture>` с `<source type=\"image/avif\">`, CDN для картинок или компонент картинок вашего фреймворка.",
|
|
359
|
+
impact: "Страницы тяжелее, чем нужно, и медленнее загружаются на телефонах.",
|
|
360
|
+
},
|
|
361
|
+
"IMG-5": {
|
|
362
|
+
title: "Картинки открываются",
|
|
363
|
+
todo: "Почините или уберите битые картинки",
|
|
364
|
+
fix: "Исправьте адрес картинок, которые отвечают ошибкой, или уберите их. Битая картинка оставляет дыру на странице и выпадает из поиска по картинкам.",
|
|
365
|
+
impact: "Посетители видят пустые рамки, а картинки выпадают из поиска по картинкам.",
|
|
366
|
+
},
|
|
367
|
+
"IMG-6": {
|
|
368
|
+
title: "Картинки не тяжелее, чем нужно",
|
|
369
|
+
todo: "Сожмите тяжёлые картинки",
|
|
370
|
+
fix: "Держите картинки легче 500 КБ: уменьшите до размера, в котором их показывают, сожмите и отдавайте телефонам версии поменьше через `srcset`.",
|
|
371
|
+
impact: "Тяжёлые картинки замедляют страницу, особенно на телефонах, и LCP ухудшается.",
|
|
372
|
+
},
|
|
373
|
+
"IMG-7": {
|
|
374
|
+
title: "Alt описывает картинку",
|
|
375
|
+
todo: "Напишите в alt, что изображено",
|
|
376
|
+
fix: "Опишите, что на картинке, коротким предложением: «Команда у доски с планом», а не «IMG_1234.jpg», «фото» или целый абзац. Для декоративных картинок — `alt=\"\"`.",
|
|
377
|
+
impact: "Поиск по картинкам и экранные дикторы получают имя файла вместо описания.",
|
|
378
|
+
},
|
|
112
379
|
"DATA-1": {
|
|
113
380
|
title: "В структурированных данных нет синтаксических ошибок",
|
|
381
|
+
todo: "Исправьте синтаксис JSON-LD",
|
|
114
382
|
fix: "Исправьте блоки JSON-LD, которые не разбираются: одна ошибка отключает весь блок, а с ним и расширенные сниппеты.",
|
|
115
383
|
impact: "Сломанная разметка игнорируется, и страницы теряют расширенные сниппеты в выдаче.",
|
|
116
384
|
},
|
|
385
|
+
"DATA-2": {
|
|
386
|
+
title: "У каждого блока есть @context, у каждой сущности — @type",
|
|
387
|
+
todo: "Добавьте в JSON-LD @context и @type",
|
|
388
|
+
fix: "Начинайте каждый блок JSON-LD с `\"@context\": \"https://schema.org\"` и указывайте каждой сущности `@type` (Person, Product, Event…). Без них поисковик не понимает, какой словарь используется, и пропускает разметку.",
|
|
389
|
+
impact: "Поисковики пропускают разметку, и у страницы нет расширенного сниппета.",
|
|
390
|
+
},
|
|
391
|
+
"DATA-3": {
|
|
392
|
+
title: "У типов для расширенных сниппетов есть обязательные поля",
|
|
393
|
+
todo: "Добавьте в разметку обязательные поля",
|
|
394
|
+
fix: "Заполните то, что Google требует для расширенного сниппета каждого типа: Product — цену, отзыв или рейтинг; Event — startDate и location; элементу BreadcrumbList — position. Проверьте в Rich Results Test.",
|
|
395
|
+
impact: "Без обязательных полей Google показывает обычный результат вместо расширенного.",
|
|
396
|
+
},
|
|
397
|
+
"DATA-4": {
|
|
398
|
+
title: "У вложенных страниц есть хлебные крошки",
|
|
399
|
+
todo: "Добавьте BreadcrumbList вложенным страницам",
|
|
400
|
+
fix: "Опишите путь к странице в JSON-LD BreadcrumbList: Главная › Блог › Статья. Тогда Google покажет под заголовком путь, а не голый адрес.",
|
|
401
|
+
impact: "В выдаче голый адрес вместо понятного пути.",
|
|
402
|
+
},
|
|
403
|
+
"DATA-5": {
|
|
404
|
+
title: "Адреса и даты в разметке понятны поисковику",
|
|
405
|
+
todo: "Пишите в разметке полные адреса и даты ISO",
|
|
406
|
+
fix: "Пишите адреса полностью (`https://example.com/logo.png`, а не `/logo.png`), а даты — в ISO 8601 (`2026-11-09` или `2026-11-09T10:00:00+03:00`, а не `09.11.2026`).",
|
|
407
|
+
impact: "Поисковик не может использовать картинку, ссылку или дату, и расширенный сниппет их теряет.",
|
|
408
|
+
},
|
|
409
|
+
"DATA-6": {
|
|
410
|
+
title: "Главная сообщает имя сайта через WebSite",
|
|
411
|
+
todo: "Добавьте на главную WebSite с названием сайта",
|
|
412
|
+
fix: "Поставьте на главную JSON-LD `{\"@type\": \"WebSite\", \"name\": \"…\", \"url\": \"https://example.com/\"}`. Из него Google берёт название сайта, которое показывает над результатами.",
|
|
413
|
+
impact: "Google угадывает название сайта над вашими результатами и может ошибиться.",
|
|
414
|
+
},
|
|
117
415
|
"LANG-1": {
|
|
118
416
|
title: "Коды hreflang правильные",
|
|
417
|
+
todo: "Исправьте коды hreflang",
|
|
119
418
|
fix: "Используйте коды вида `ru`, `en`, `en-GB` или `x-default`. Из-за неверного кода поисковик игнорирует весь набор.",
|
|
120
419
|
impact: "Неверные коды языков игнорируются, и в выдаче люди получают не ту языковую версию.",
|
|
121
420
|
},
|
|
122
421
|
"LANG-2": {
|
|
123
422
|
title: "Языковые версии ссылаются друг на друга",
|
|
423
|
+
todo: "Сделайте ссылки hreflang взаимными",
|
|
124
424
|
fix: "Если страница A указывает страницу B как перевод, B должна указывать A. Односторонние hreflang игнорируются.",
|
|
125
425
|
impact: "Односторонние языковые ссылки игнорируются, и поиск показывает не ту языковую версию.",
|
|
126
426
|
},
|
|
127
427
|
"LANG-3": {
|
|
128
428
|
title: "Есть x-default для остальных языков",
|
|
429
|
+
todo: "Добавьте hreflang x-default",
|
|
129
430
|
fix: 'Добавьте `<link rel="alternate" hreflang="x-default" href="…">` на версию для всех остальных.',
|
|
130
431
|
impact: "Посетители с языком, которого нет на сайте, получают случайную версию.",
|
|
131
432
|
},
|
|
433
|
+
"LANG-4": {
|
|
434
|
+
title: "Каждая версия указывает в hreflang и себя",
|
|
435
|
+
todo: "Добавьте страницу в её собственный список hreflang",
|
|
436
|
+
fix: "Перечисляйте на каждой языковой версии все версии, включая её саму: на русской странице рядом с остальными стоит `hreflang=\"ru\"` на её же адрес.",
|
|
437
|
+
impact: "Поисковики могут не учитывать набор языков и показывать не ту версию.",
|
|
438
|
+
},
|
|
439
|
+
"LANG-5": {
|
|
440
|
+
title: "hreflang ведёт на рабочие канонические страницы",
|
|
441
|
+
todo: "Указывайте в hreflang конечный канонический адрес каждой версии",
|
|
442
|
+
fix: "Ссылайтесь на языковую версию по адресу, который отвечает 200 и указывает canonical на себя: не на редирект, не на ошибку и не на копию с canonical на другую страницу.",
|
|
443
|
+
impact: "Поисковики не учитывают языковые ссылки на сломанные, редиректящие или неканонические страницы.",
|
|
444
|
+
},
|
|
445
|
+
"LANG-6": {
|
|
446
|
+
title: "Язык в hreflang совпадает с языком страницы",
|
|
447
|
+
todo: "Исправьте коды языков или адреса в hreflang",
|
|
448
|
+
fix: "Код должен совпадать со страницей: `hreflang=\"ru\"` ведёт на страницу с `<html lang=\"ru\">`. Обычно перепутаны два кода или шаблон ведёт все языки на одну страницу.",
|
|
449
|
+
impact: "Людей, которые ищут на одном языке, отправляют на страницу на другом.",
|
|
450
|
+
},
|
|
451
|
+
"LANG-7": {
|
|
452
|
+
title: "Каждый язык указан один раз",
|
|
453
|
+
todo: "Оставьте в hreflang один адрес на язык",
|
|
454
|
+
fix: "Давайте каждому коду языка один адрес. Два адреса для `en` обычно значат, что региональной версии нужен свой код: `en-GB` и `en-US`.",
|
|
455
|
+
impact: "Поисковик не понимает, какой адрес показывать для этого языка, и может не учитывать оба.",
|
|
456
|
+
},
|
|
457
|
+
"LANG-8": {
|
|
458
|
+
title: "Адреса в hreflang полные",
|
|
459
|
+
todo: "Пишите в hreflang полные адреса",
|
|
460
|
+
fix: "Указывайте адреса с протоколом и доменом: `https://example.com/ru/`, а не `/ru/`. Относительные адреса в hreflang Google не принимает.",
|
|
461
|
+
impact: "Google не учитывает относительные языковые ссылки.",
|
|
462
|
+
},
|
|
132
463
|
"SEC-1": {
|
|
133
464
|
title: "Сайт работает по HTTPS, а http:// на него редиректит",
|
|
465
|
+
todo: "Переведите сайт на HTTPS с редиректом с http://",
|
|
134
466
|
fix: "Отдавайте сайт по https и перенаправляйте каждый адрес http:// на его https:// версию с кодом 301.",
|
|
135
467
|
impact: "Без HTTPS браузеры помечают сайт как небезопасный, а поисковики предпочитают защищённых конкурентов.",
|
|
136
468
|
},
|
|
137
469
|
"SEC-2": {
|
|
138
470
|
title: "HTTPS-страницы ничего не грузят по http://",
|
|
471
|
+
todo: "Загружайте все ресурсы по https",
|
|
139
472
|
fix: "Загружайте картинки, скрипты и стили по https. Браузеры блокируют смешанный контент или предупреждают о нём.",
|
|
140
473
|
impact: "Смешанный контент ломает части страницы в браузерах, и сайт выглядит небезопасным.",
|
|
141
474
|
},
|
|
475
|
+
"SEC-3": {
|
|
476
|
+
title: "http:// постоянно редиректит на https://",
|
|
477
|
+
todo: "Сделайте редирект с http:// постоянным (301)",
|
|
478
|
+
fix: "Отвечайте на http:// кодом 301 (или 308) на https://-адрес. 302 и 307 значат «временно»: поисковики оставляют http://-версию и не передают её вес на https://.",
|
|
479
|
+
impact: "Поисковики держат старый http://-адрес, и https://-версия не получает его вес.",
|
|
480
|
+
},
|
|
481
|
+
"SEC-4": {
|
|
482
|
+
title: "Сертификат действителен и не истекает",
|
|
483
|
+
todo: "Обновите TLS-сертификат и включите автопродление",
|
|
484
|
+
fix: "Поставьте сертификат, которому доверяют браузеры, выданный на ваш домен и не просроченный, и продлевайте его автоматически (Let's Encrypt с certbot или сертификат CDN). Сайт с просроченным или чужим сертификатом браузеры блокируют.",
|
|
485
|
+
impact: "Браузеры показывают вместо сайта предупреждение на весь экран, и посетители с краулерами уходят.",
|
|
486
|
+
},
|
|
487
|
+
"SEC-5": {
|
|
488
|
+
title: "Браузерам велено ходить только по https (HSTS)",
|
|
489
|
+
todo: "Добавьте заголовок Strict-Transport-Security",
|
|
490
|
+
fix: "Отдавайте на https-ответах `Strict-Transport-Security: max-age=31536000; includeSubDomains`. Тогда браузер сразу идёт на https:// без редиректа и без момента, когда соединение можно перехватить.",
|
|
491
|
+
impact: "Каждый первый заход идёт через http://: это медленнее и его можно перехватить.",
|
|
492
|
+
},
|
|
493
|
+
"SEC-6": {
|
|
494
|
+
title: "Адреса сайта указаны с https://",
|
|
495
|
+
todo: "Замените http:// на https:// в ссылках, canonical, sitemap и og-тегах",
|
|
496
|
+
fix: "Пишите все адреса сайта с https://: внутренние ссылки, canonical, hreflang, og:url и og:image, sitemap. Каждый http://-адрес стоит лишнего редиректа, а canonical на него называет не ту версию.",
|
|
497
|
+
impact: "Поисковики и посетители ходят через лишние редиректы, а canonical указывает на незащищённую версию.",
|
|
498
|
+
},
|
|
499
|
+
"SEC-7": {
|
|
500
|
+
title: "Есть базовые заголовки безопасности",
|
|
501
|
+
todo: "Добавьте заголовки nosniff, защиту от встраивания и Referrer-Policy",
|
|
502
|
+
fix: "Отдавайте `X-Content-Type-Options: nosniff`, `X-Frame-Options: SAMEORIGIN` (или CSP `frame-ancestors 'self'`) и `Referrer-Policy: strict-origin-when-cross-origin`. На позиции они не влияют, но защищают посетителей от частых атак.",
|
|
503
|
+
impact: "Сайт проще атаковать: подмена типа файла, встраивание в чужую страницу, утечка адресов.",
|
|
504
|
+
},
|
|
142
505
|
},
|
|
143
506
|
m: {
|
|
507
|
+
robotsStatus: (status) => `robots.txt отвечает ${status}: пока он не ответит, Google считает весь сайт закрытым`,
|
|
508
|
+
robotsHtml: "robots.txt отдаёт HTML-страницу вместо текстовых правил",
|
|
509
|
+
robotsSize: (kb) => `robots.txt весит ${kb} КБ: Google читает только первые 500 КБ`,
|
|
510
|
+
robotsBadLines: (n, line) => `непонятных поисковикам строк: ${n}, например «${line}»`,
|
|
511
|
+
blockedPage: (bots) => `robots.txt закрывает её для ${bots}`,
|
|
512
|
+
blockedBySitemap: (bots) => `есть в sitemap, но robots.txt закрывает её для ${bots}`,
|
|
513
|
+
otherHostAnswers: (url, status) => `${url} отвечает ${status} вместо редиректа на основной адрес`,
|
|
514
|
+
slashDuplicate: (url) => `${url} тоже отвечает 200, без редиректа и без canonical на эту страницу`,
|
|
515
|
+
redirectChain: (url, n, path) => `${url}: редиректов подряд — ${n}: ${path}`,
|
|
516
|
+
redirectLoop: (url, path) => `${url} уходит в петлю редиректов: ${path}`,
|
|
517
|
+
noindexHome: "главная закрыта noindex: сайт выпадает из поиска",
|
|
518
|
+
noindexInSitemap: "есть в sitemap, но закрыта noindex",
|
|
519
|
+
noindexWithCanonical: (href) => `noindex вместе с canonical на ${href}: два разных сигнала`,
|
|
520
|
+
canonicalToNoindex: (href) => `canonical ведёт на ${href}, а она закрыта noindex`,
|
|
521
|
+
blockedResource: (url) => `robots.txt закрывает ${url} для Googlebot`,
|
|
522
|
+
slowServer: (ms, limit) => `главная начинает приходить через ${(ms / 1000).toFixed(1).replace(".", ",")} с (медиана из 3 запросов), нужно до ${String(limit / 1000).replace(".", ",")} с`,
|
|
523
|
+
skipNoRobots: "нет robots.txt",
|
|
524
|
+
skipNoSlashPage: "среди проверенных нет страниц, кроме главной",
|
|
525
|
+
temporaryRedirect: (status) => `отвечает ${status}, временным редиректом, вместо 301`,
|
|
526
|
+
certExpired: (date) => `сертификат истёк ${date}`,
|
|
527
|
+
certInvalid: (error) => `браузеры не доверяют сертификату: ${error}`,
|
|
528
|
+
certExpiring: (days, date) => `сертификат истекает через ${days} дн., ${date}`,
|
|
529
|
+
noHsts: "нет заголовка Strict-Transport-Security",
|
|
530
|
+
shortHsts: (days) => `max-age в HSTS — ${days} дн.; нужно не меньше 180`,
|
|
531
|
+
httpAddress: (where, url) => `${where} с http://: ${url}`,
|
|
532
|
+
httpLinks: (n, url) => `внутренних ссылок с http://: ${n}, например ${url}`,
|
|
533
|
+
httpInSitemap: (n, url) => `адресов с http://: ${n}, например ${url}`,
|
|
534
|
+
missingHeaders: (names) => `нет: ${names}`,
|
|
535
|
+
manyCanonicals: (n, hrefs) => `разных canonical на странице: ${n} — ${hrefs}`,
|
|
536
|
+
canonicalInBody: (href) => `canonical ${href} стоит в <body>`,
|
|
537
|
+
canonicalConflict: (html, header) => `в HTML указан ${html}, в заголовке Link — ${header}`,
|
|
538
|
+
canonicalChain: (target, next) => `canonical ведёт на ${target}, а её canonical — ${next}`,
|
|
539
|
+
utmNoCanonical: (url) => `у ${url} нет canonical`,
|
|
540
|
+
utmCanonical: (url) => `${url} указывает canonical на себя вместе с UTM-метками`,
|
|
541
|
+
canonicalOtherLanguage: (href, to, from) => `canonical ведёт на ${href} — версию «${to}» для страницы «${from}»`,
|
|
542
|
+
sitemapStatus: (status) => `отвечает ${status}`,
|
|
543
|
+
sitemapHtml: "отдаёт HTML-страницу вместо XML",
|
|
544
|
+
sitemapNotXml: "нет ни <urlset>, ни <sitemapindex>",
|
|
545
|
+
sitemapTooMany: (n) => `адресов: ${n}, больше лимита в 50 000 на файл`,
|
|
546
|
+
sitemapTooBig: (mb) => `${mb} МБ, больше лимита в 50 МБ на файл`,
|
|
547
|
+
sitemapAmpersand: (n) => `адресов с неэкранированным &, который ломает XML: ${n}`,
|
|
548
|
+
sitemapForeign: (n, example) => `относительных адресов или адресов другого сайта: ${n}, например ${example}`,
|
|
549
|
+
notInSitemap: (n) => `на неё ссылаются проверенные страницы (${n}), но в sitemap её нет`,
|
|
550
|
+
sitemapDead: (status) => `есть в sitemap, но отвечает ${status}`,
|
|
551
|
+
lastmodBad: (n, example) => `lastmod не в формате W3C: ${n}, например «${example}»`,
|
|
552
|
+
lastmodFuture: (n, example) => `lastmod в будущем: ${n}, например ${example}`,
|
|
553
|
+
lastmodSame: (n, value) => `у всех ${n} адресов одинаковый lastmod, ${value}: похоже на время сборки`,
|
|
554
|
+
skipNoLastmod: "в sitemap нет lastmod",
|
|
555
|
+
manyTitles: (n) => `тегов <title> в <head>: ${n}`,
|
|
556
|
+
manyDescriptions: (n) => `meta description в <head>: ${n}`,
|
|
557
|
+
genericTitle: (title) => `title — «${title}»`,
|
|
558
|
+
descriptionRepeatsTitle: "description повторяет title",
|
|
559
|
+
noCharset: "нет charset ни в заголовке Content-Type, ни в <meta charset>",
|
|
560
|
+
noSnippet: (rule) => `сниппет запрещён: ${rule}`,
|
|
561
|
+
noFavicon: (status) => `в <head> нет иконки, а /favicon.ico отвечает ${status}`,
|
|
562
|
+
emptyH1: "в H1 нет текста: только картинка или ничего",
|
|
563
|
+
emptyHeadings: (n, levels) => `пустых заголовков: ${n} — ${levels}`,
|
|
564
|
+
titleH1Apart: (title, h1) => `ни одного общего слова: title «${title}», H1 «${h1}»`,
|
|
565
|
+
noZoom: (viewport) => `масштабирование запрещено: ${viewport}`,
|
|
566
|
+
langMismatch: (lang, script, percent) => `<html lang="${lang}">, но ${percent}% букв — ${script}`,
|
|
567
|
+
scriptCyrillic: "кириллица",
|
|
568
|
+
scriptLatin: "латиница",
|
|
569
|
+
noMain: "на странице нет <main>",
|
|
570
|
+
noDoctype: "в начале нет <!doctype html>",
|
|
571
|
+
orphan: "на страницу не ведёт ни одна проверенная страница: о ней знает только sitemap",
|
|
572
|
+
uncrawlableLinks: (n, text) => `ссылок без адреса для перехода (нет href, # или javascript:): ${n}, например «${text}»`,
|
|
573
|
+
nofollowInternal: (n, url) => `внутренних ссылок с nofollow: ${n}, например на ${url}`,
|
|
574
|
+
vagueLinks: (n, text) => `ссылок, текст которых ничего не говорит о цели: ${n}, например «${text}»`,
|
|
575
|
+
namelessLinks: (n, href) => `ссылок без текста, подписи и alt: ${n}, например на ${href}`,
|
|
576
|
+
deadExternal: (url, status) => `ссылка на ${url} отвечает ${status}`,
|
|
577
|
+
skipPartialSample: "проверены не все страницы из sitemap: запустите с --pages all",
|
|
578
|
+
skipNoExternal: "на проверенных страницах нет ссылок на другие сайты",
|
|
579
|
+
skipNoNetwork: "ни один внешний сайт не ответил: у проверки нет доступа в интернет",
|
|
580
|
+
imagesNoSize: (n, total, src) => `без width и height ${n} из ${total} картинок, например ${src}`,
|
|
581
|
+
firstImageLazy: (src) => `первая картинка загружается лениво: ${src}`,
|
|
582
|
+
noLazyImages: (n) => `картинок: ${n}, и ни одна не загружается лениво`,
|
|
583
|
+
oldFormats: (n, src) => `картинок только в JPEG или PNG: ${n}, например ${src}`,
|
|
584
|
+
brokenImage: (src, status) => `картинка ${src} отвечает ${status}`,
|
|
585
|
+
heavyImage: (src, kb, max) => `картинка ${src} весит ${kb} КБ, нужно до ${max} КБ`,
|
|
586
|
+
poorAlt: (n, alt) => `alt, который не описывает картинку: ${n}, например «${alt}»`,
|
|
587
|
+
noContext: "блок JSON-LD без @context schema.org",
|
|
588
|
+
noType: (n) => `сущностей без @type: ${n}`,
|
|
589
|
+
missingProperties: (type, keys) => `${type} без ${keys}`,
|
|
590
|
+
noBreadcrumbs: "нет BreadcrumbList",
|
|
591
|
+
relativeInMarkup: (n, example) => `неполных адресов: ${n}, например ${example}`,
|
|
592
|
+
badDatesInMarkup: (n, example) => `дат не в ISO 8601: ${n}, например ${example}`,
|
|
593
|
+
noWebSite: "на главной нет WebSite с названием",
|
|
594
|
+
skipNoRichTypes: "на проверенных страницах нет типов с расширенными сниппетами",
|
|
595
|
+
skipNoNestedPages: "среди проверенных нет вложенных страниц",
|
|
596
|
+
noSelfHreflang: "страницы нет в её собственном списке hreflang",
|
|
597
|
+
hreflangStatus: (lang, url, status) => `«${lang}» ведёт на ${url}, а та отвечает ${status}`,
|
|
598
|
+
hreflangRedirects: (lang, url, to) => `«${lang}» ведёт на ${url}, а та редиректит на ${to}`,
|
|
599
|
+
hreflangNotCanonical: (lang, url, canonical) => `«${lang}» ведёт на ${url}, а её canonical — ${canonical}`,
|
|
600
|
+
hreflangLangMismatch: (lang, url, htmlLang) => `«${lang}» ведёт на ${url} — страницу с lang="${htmlLang}"`,
|
|
601
|
+
duplicateHreflang: (codes) => `больше одного адреса для ${codes}`,
|
|
602
|
+
relativeHreflang: (n, href) => `относительных адресов: ${n}, например ${href}`,
|
|
144
603
|
status: (status) => `отвечает ${status}`,
|
|
145
604
|
soft404Probe: (url) => `выдуманный адрес ${url} отвечает 200 вместо 404`,
|
|
146
605
|
soft404Page: (title) => `отвечает 200, но похожа на страницу «не найдено»: «${title}»`,
|
|
@@ -154,18 +613,18 @@ export const ru = {
|
|
|
154
613
|
sitemapRedirects: (to) => `есть в sitemap, редиректит на ${to}`,
|
|
155
614
|
sitemapCanonicalElsewhere: (href) => `есть в sitemap, но её canonical — ${href}`,
|
|
156
615
|
noTitle: "нет <title>",
|
|
157
|
-
titleLength: (n, title) => `title на ${n}
|
|
616
|
+
titleLength: (n, title, min, max) => `title на ${n} ${chars(n)}, нужно ${min}–${max}: «${title}»`,
|
|
158
617
|
noDescription: "нет meta description",
|
|
159
|
-
descriptionLength: (n) => `description на ${n}
|
|
160
|
-
sameTitle: (n, title) => `одинаковый title на ${n}
|
|
161
|
-
sameDescription: (n, text) => `одинаковый description на ${n}
|
|
618
|
+
descriptionLength: (n, min, max) => `description на ${n} ${chars(n)}, нужно ${min}–${max}`,
|
|
619
|
+
sameTitle: (n, title) => `одинаковый title на ${n} страницах, у каждой должен быть свой: «${title}»`,
|
|
620
|
+
sameDescription: (n, text) => `одинаковый description на ${n} страницах, у каждой должен быть свой: «${text}»`,
|
|
162
621
|
noH1: "нет H1",
|
|
163
|
-
manyH1: (n) => `заголовков H1: ${n}
|
|
164
|
-
skippedLevel: (from, to, text) => `после H${from} идёт H${to}
|
|
622
|
+
manyH1: (n) => `заголовков H1: ${n}, нужен один`,
|
|
623
|
+
skippedLevel: (from, to, text) => `после H${from} идёт H${to}, уровень пропущен: «${text}»`,
|
|
165
624
|
noLang: "нет <html lang>",
|
|
166
625
|
noViewport: "нет meta viewport",
|
|
167
|
-
brokenLink: (
|
|
168
|
-
redirectLink: (
|
|
626
|
+
brokenLink: (url, status) => `ссылка на ${url} отвечает ${status}`,
|
|
627
|
+
redirectLink: (url, to) => `ссылка на ${url} редиректит на ${to}`,
|
|
169
628
|
linksNotChecked: (checked, found) => `проверено внутренних ссылок: ${checked} из ${found}; чтобы проверить больше, увеличьте --max-links`,
|
|
170
629
|
imagesNoAlt: (n, total, example) => `без alt ${n} из ${total} картинок, например ${example}`,
|
|
171
630
|
jsonLdError: (error) => `JSON-LD не разбирается: ${error}`,
|
|
@@ -180,6 +639,7 @@ export const ru = {
|
|
|
180
639
|
skipNoHreflang: "на сайте нет hreflang",
|
|
181
640
|
skipOneLanguage: "на сайте один язык",
|
|
182
641
|
skipNoImages: "на проверенных страницах нет картинок",
|
|
642
|
+
skipNoOwnImages: "на проверенных страницах нет картинок этого сайта: чужие не запрашиваются",
|
|
183
643
|
skipNoJsonLd: "на проверенных страницах нет структурированных данных",
|
|
184
644
|
skipNoLinks: "на проверенных страницах нет внутренних ссылок",
|
|
185
645
|
skipNotHttps: "сайт не на https",
|