@rivi-software/agentic-seo-check 0.1.1 → 0.3.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.
Files changed (110) hide show
  1. package/README.md +67 -7
  2. package/dist/cli.js +69 -26
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/aeo/audit.d.ts +44 -0
  5. package/dist/core/aeo/audit.js +310 -0
  6. package/dist/core/aeo/audit.js.map +1 -0
  7. package/dist/core/aeo/bots.d.ts +30 -0
  8. package/dist/core/aeo/bots.js +299 -0
  9. package/dist/core/aeo/bots.js.map +1 -0
  10. package/dist/core/aeo/llms-txt.d.ts +8 -0
  11. package/dist/core/aeo/llms-txt.js +88 -0
  12. package/dist/core/aeo/llms-txt.js.map +1 -0
  13. package/dist/core/aeo/page.d.ts +43 -0
  14. package/dist/core/aeo/page.js +284 -0
  15. package/dist/core/aeo/page.js.map +1 -0
  16. package/dist/core/aeo/probe.d.ts +10 -0
  17. package/dist/core/aeo/probe.js +53 -0
  18. package/dist/core/aeo/probe.js.map +1 -0
  19. package/dist/core/aeo/site-rules.d.ts +13 -0
  20. package/dist/core/aeo/site-rules.js +178 -0
  21. package/dist/core/aeo/site-rules.js.map +1 -0
  22. package/dist/core/audit.d.ts +23 -2
  23. package/dist/core/audit.js +50 -22
  24. package/dist/core/audit.js.map +1 -1
  25. package/dist/core/crawl.d.ts +7 -1
  26. package/dist/core/crawl.js +85 -9
  27. package/dist/core/crawl.js.map +1 -1
  28. package/dist/core/fetch.d.ts +9 -0
  29. package/dist/core/fetch.js +28 -12
  30. package/dist/core/fetch.js.map +1 -1
  31. package/dist/core/html/answer.d.ts +4 -0
  32. package/dist/core/html/answer.js +198 -0
  33. package/dist/core/html/answer.js.map +1 -0
  34. package/dist/core/html/dom.d.ts +6 -0
  35. package/dist/core/html/dom.js +92 -1
  36. package/dist/core/html/dom.js.map +1 -1
  37. package/dist/core/html/extract.js +21 -72
  38. package/dist/core/html/extract.js.map +1 -1
  39. package/dist/core/html/head.d.ts +10 -0
  40. package/dist/core/html/head.js +206 -0
  41. package/dist/core/html/head.js.map +1 -0
  42. package/dist/core/html/resources.d.ts +17 -0
  43. package/dist/core/html/resources.js +172 -0
  44. package/dist/core/html/resources.js.map +1 -0
  45. package/dist/core/html/structured-data.d.ts +2 -0
  46. package/dist/core/html/structured-data.js +2 -0
  47. package/dist/core/html/structured-data.js.map +1 -1
  48. package/dist/core/report/markdown.d.ts +3 -1
  49. package/dist/core/report/markdown.js +102 -0
  50. package/dist/core/report/markdown.js.map +1 -1
  51. package/dist/core/robots.d.ts +7 -0
  52. package/dist/core/robots.js +13 -1
  53. package/dist/core/robots.js.map +1 -1
  54. package/dist/core/rules/aeo.d.ts +11 -0
  55. package/dist/core/rules/aeo.js +423 -0
  56. package/dist/core/rules/aeo.js.map +1 -0
  57. package/dist/core/rules/canonical.d.ts +3 -0
  58. package/dist/core/rules/canonical.js +54 -2
  59. package/dist/core/rules/canonical.js.map +1 -1
  60. package/dist/core/rules/document.js +4 -2
  61. package/dist/core/rules/document.js.map +1 -1
  62. package/dist/core/rules/head.d.ts +1 -0
  63. package/dist/core/rules/head.js +116 -0
  64. package/dist/core/rules/head.js.map +1 -0
  65. package/dist/core/rules/index.d.ts +4 -0
  66. package/dist/core/rules/index.js +15 -1
  67. package/dist/core/rules/index.js.map +1 -1
  68. package/dist/core/rules/indexing.d.ts +10 -0
  69. package/dist/core/rules/indexing.js +171 -9
  70. package/dist/core/rules/indexing.js.map +1 -1
  71. package/dist/core/rules/loading.d.ts +1 -0
  72. package/dist/core/rules/loading.js +138 -0
  73. package/dist/core/rules/loading.js.map +1 -0
  74. package/dist/core/rules/rule.d.ts +2 -0
  75. package/dist/core/rules/rule.js +2 -0
  76. package/dist/core/rules/rule.js.map +1 -1
  77. package/dist/core/rules/structured-data-values.d.ts +3 -0
  78. package/dist/core/rules/structured-data-values.js +316 -0
  79. package/dist/core/rules/structured-data-values.js.map +1 -0
  80. package/dist/core/rules/structured-data.js +50 -20
  81. package/dist/core/rules/structured-data.js.map +1 -1
  82. package/dist/core/rules/urls.d.ts +1 -0
  83. package/dist/core/rules/urls.js +293 -0
  84. package/dist/core/rules/urls.js.map +1 -0
  85. package/dist/core/score.d.ts +4 -2
  86. package/dist/core/score.js.map +1 -1
  87. package/dist/core/site-probes.d.ts +33 -0
  88. package/dist/core/site-probes.js +144 -0
  89. package/dist/core/site-probes.js.map +1 -0
  90. package/dist/core/site-rules.d.ts +18 -0
  91. package/dist/core/site-rules.js +479 -5
  92. package/dist/core/site-rules.js.map +1 -1
  93. package/dist/core/sitemap.d.ts +38 -1
  94. package/dist/core/sitemap.js +177 -43
  95. package/dist/core/sitemap.js.map +1 -1
  96. package/dist/core/static.d.ts +6 -0
  97. package/dist/core/static.js +122 -12
  98. package/dist/core/static.js.map +1 -1
  99. package/dist/core/types.d.ts +268 -2
  100. package/dist/core/types.js.map +1 -1
  101. package/dist/core/url.d.ts +2 -0
  102. package/dist/core/url.js +8 -0
  103. package/dist/core/url.js.map +1 -1
  104. package/dist/index.d.ts +6 -2
  105. package/dist/index.js +4 -2
  106. package/dist/index.js.map +1 -1
  107. package/dist/mcp.js +129 -3
  108. package/dist/mcp.js.map +1 -1
  109. package/package.json +6 -2
  110. package/skills/seo-audit/SKILL.md +9 -2
@@ -0,0 +1,116 @@
1
+ import { normalizeUrl, resolveUrl } from "../url.js";
2
+ import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
3
+ /** The HTML spec's limit for the element that declares the encoding. */
4
+ const CHARSET_PRESCAN_BYTES = 1024;
5
+ const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
6
+ export const headRules = [
7
+ defineRule({
8
+ id: "head.closed-early",
9
+ category: "document",
10
+ severity: "error",
11
+ description: "An element that does not belong in <head> (an <img>, <iframe>, <div>, stray text) ends it early, so search engines read the metadata after it as part of <body> and ignore it.",
12
+ recommendation: "Move the element out of <head>, into <body>, or at least below every <title>, <meta> and <link>. Only <title>, <meta>, <link>, <script>, <style>, <base>, <noscript> and <template> belong in <head>. A tag manager's or ad pixel's <noscript><iframe> or <img> goes right after the opening <body> tag.",
13
+ docs: GOOGLE_DOCS.validMetadata,
14
+ }, ({ page }, make) => {
15
+ const { closedBy, closedInNoscript, afterClose } = page.head;
16
+ if (!closedBy || afterClose.length === 0) {
17
+ return null;
18
+ }
19
+ if (closedInNoscript) {
20
+ return make(`A <noscript> in <head> holds ${closedBy}, which ends <head> for a parser that does not run JavaScript, so it reads the ${plural(afterClose.length, "metadata element")} after it as <body>: ${cap(afterClose, 5).join(", ")}.`, {
21
+ severity: "info",
22
+ recommendation: "Move the <noscript> block below every <title>, <meta> and <link>, or right after the opening <body> tag, where tag managers and ad pixels document it. Inside a <noscript> in <head>, only <link>, <meta> and <style> keep <head> open.",
23
+ selector: "head > noscript",
24
+ evidence: {
25
+ closedBy,
26
+ inNoscript: true,
27
+ metadataAfter: cap(afterClose),
28
+ },
29
+ });
30
+ }
31
+ // Losing the viewport costs the mobile layout; losing the rest costs what Google indexes.
32
+ const onlyViewport = afterClose.every((m) => m === '<meta name="viewport">');
33
+ // An invisible character is removed, not moved: it usually comes from a file saved with a BOM.
34
+ const invisible = closedBy.startsWith("invisible character");
35
+ return make(`<head> ends at ${closedBy}, so Google ignores the ${plural(afterClose.length, "metadata element")} after it: ${cap(afterClose, 5).join(", ")}.`, {
36
+ severity: onlyViewport ? "warning" : "error",
37
+ recommendation: invisible
38
+ ? "Remove the invisible character from <head>. A U+FEFF byte order mark usually comes from a template or include file saved as UTF-8 with BOM: save it as UTF-8 without BOM. A U+00A0 comes from an &nbsp; in the template's <head>."
39
+ : undefined,
40
+ selector: "head",
41
+ evidence: {
42
+ closedBy,
43
+ inNoscript: false,
44
+ metadataAfter: cap(afterClose),
45
+ },
46
+ });
47
+ }),
48
+ defineRule({
49
+ id: "head.metadata-in-body",
50
+ category: "document",
51
+ severity: "warning",
52
+ description: "Metadata that search engines read only in <head> (title, meta description, viewport, canonical, hreflang) is written in <body> (warning). Metadata a framework streams into a hidden part of <body> and moves into <head> with JavaScript, as Next.js does for an async generateMetadata, is info. Robots meta tags are not reported: Google honours them in <body> too.",
53
+ recommendation: "Move these elements into <head>. Google accepts rel=canonical only in <head>. A framework that renders them in a page component usually has a head API for them (metadata export, <Head>, useHead, <svelte:head>).",
54
+ docs: GOOGLE_DOCS.validMetadata,
55
+ }, ({ page }, make) => {
56
+ const { inBody, streamedInBody } = page.head;
57
+ if (inBody.length > 0) {
58
+ return make(`${plural(inBody.length, "metadata element")} ${inBody.length === 1 ? "is" : "are"} in <body>: ${cap(inBody, 5).join(", ")}.`, { selector: "body", evidence: { elements: cap(inBody) } });
59
+ }
60
+ // Next.js (15.2+) streams metadata from an async generateMetadata this way to clients it does
61
+ // not count as HTML-limited bots; Googlebot reads it after rendering.
62
+ return streamedInBody.length > 0
63
+ ? make(`${plural(streamedInBody.length, "metadata element")} ${streamedInBody.length === 1 ? "is" : "are"} streamed into a hidden part of <body> (React or Next.js streaming metadata): ${cap(streamedInBody, 5).join(", ")}.`, {
64
+ severity: "info",
65
+ recommendation: "Google renders the page and reads streamed metadata, so this is fine for Google Search. Crawlers that do not run JavaScript only get it in <head> if the framework holds the response for them: in Next.js, add their User-Agents to htmlLimitedBots in next.config, or make generateMetadata static.",
66
+ selector: "body [hidden]",
67
+ evidence: { elements: cap(streamedInBody), streamed: true },
68
+ })
69
+ : null;
70
+ }),
71
+ defineRule({
72
+ id: "meta-refresh.redirect",
73
+ category: "indexing",
74
+ severity: "warning",
75
+ description: 'A <meta http-equiv="refresh"> sends visitors to another URL. Google reads an instant one as a permanent redirect (info) and a delayed one as a temporary redirect (warning), and prefers a redirect from the server.',
76
+ recommendation: "Redirect on the server with a 301 (or 308) to the new URL. If the server cannot redirect, set the meta refresh to 0 seconds, which Google reads as a permanent redirect.",
77
+ docs: GOOGLE_DOCS.redirects,
78
+ }, ({ page }, make) => {
79
+ const redirect = page.head.refresh.find((r) => r.url);
80
+ if (!redirect?.url) {
81
+ return null;
82
+ }
83
+ const target = resolveUrl(redirect.url, page.url) ?? redirect.url;
84
+ const delay = redirect.seconds ?? 0;
85
+ // A refresh to the page's own URL reloads it (a news front page); it redirects nowhere.
86
+ if ((normalizeUrl(target) ?? target) ===
87
+ (normalizeUrl(page.url) ?? page.url)) {
88
+ return null;
89
+ }
90
+ return make(delay > 0
91
+ ? `Page redirects to ${target} with a meta refresh after ${plural(delay, "second")}, which Google treats as a temporary redirect.`
92
+ : `Page redirects to ${target} with an instant meta refresh.`, {
93
+ severity: delay > 0 ? "warning" : "info",
94
+ selector: 'meta[http-equiv="refresh"]',
95
+ evidence: { target, seconds: redirect.seconds },
96
+ });
97
+ }),
98
+ defineRule({
99
+ id: "charset.late",
100
+ category: "document",
101
+ severity: "info",
102
+ description: "The character encoding is declared after the first 1024 bytes, where the HTML spec requires it, and the Content-Type header does not declare it.",
103
+ recommendation: 'Put <meta charset="utf-8"> first in <head>, before any script, style or comment, or send the encoding in the Content-Type header (text/html; charset=utf-8).',
104
+ docs: "https://html.spec.whatwg.org/multipage/semantics.html#charset",
105
+ }, ({ page, response }, make) => {
106
+ const end = page.head.charsetEndByte;
107
+ if (end === null || end <= CHARSET_PRESCAN_BYTES) {
108
+ return null;
109
+ }
110
+ if (/charset=/iu.test(response?.contentType ?? "")) {
111
+ return null;
112
+ }
113
+ return make(`The charset declaration ends at byte ${end}, past the first ${CHARSET_PRESCAN_BYTES}.`, { selector: "head > meta[charset]", evidence: { endsAtByte: end } });
114
+ }),
115
+ ];
116
+ //# sourceMappingURL=head.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.js","sourceRoot":"","sources":["../../../src/core/rules/head.ts"],"names":[],"mappings":"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"}
@@ -3,11 +3,15 @@ import type { PageContext, Rule } from "./rule.js";
3
3
  export type { PageContext, Rule } from "./rule.js";
4
4
  /** Rules evaluated against a single HTML document. */
5
5
  export declare const PAGE_RULES: Rule[];
6
+ /** Rules the AEO audit evaluates against each page; the SEO audit never runs them. */
7
+ export declare const AEO_PAGE_RULES: Rule[];
6
8
  /** Metadata for every rule the tool can emit, including rules implemented outside the page-rule loop. */
7
9
  export declare const ALL_RULES: RuleMeta[];
8
10
  export interface RunRulesOptions {
9
11
  /** Rule ids to skip. */
10
12
  disable?: Set<string>;
13
+ /** The rules to run (default: the SEO page rules). */
14
+ rules?: Rule[];
11
15
  }
12
16
  export declare const sortFindings: (findings: Finding[]) => Finding[];
13
17
  export declare const runPageRules: (ctx: PageContext, options?: RunRulesOptions) => Finding[];
@@ -1,20 +1,26 @@
1
+ import { AEO_SITE_RULES } from "../aeo/site-rules.js";
1
2
  import { PERFORMANCE_RULES } from "../performance-rules.js";
2
3
  import { RENDER_RULES } from "../render-rules.js";
3
4
  import { SITE_RULES } from "../site-rules.js";
5
+ import { aeoRules } from "./aeo.js";
4
6
  import { canonicalRules } from "./canonical.js";
5
7
  import { contentRules } from "./content.js";
6
8
  import { documentRules } from "./document.js";
9
+ import { headRules } from "./head.js";
7
10
  import { headingRules } from "./headings.js";
8
11
  import { hreflangRules } from "./hreflang.js";
9
12
  import { imageRules } from "./images.js";
10
13
  import { indexingRules } from "./indexing.js";
11
14
  import { keywordRules } from "./keyword.js";
12
15
  import { linkRules } from "./links.js";
16
+ import { loadingRules } from "./loading.js";
13
17
  import { metaRules } from "./meta.js";
14
18
  import { responseRules } from "./response.js";
15
19
  import { socialRules } from "./social.js";
20
+ import { structuredDataValueRules } from "./structured-data-values.js";
16
21
  import { structuredDataRules } from "./structured-data.js";
17
22
  import { titleRules } from "./title.js";
23
+ import { urlRules } from "./urls.js";
18
24
  /** Rules evaluated against a single HTML document. */
19
25
  export const PAGE_RULES = [
20
26
  ...titleRules,
@@ -23,21 +29,29 @@ export const PAGE_RULES = [
23
29
  ...canonicalRules,
24
30
  ...indexingRules,
25
31
  ...documentRules,
32
+ ...headRules,
26
33
  ...socialRules,
27
34
  ...imageRules,
28
35
  ...linkRules,
36
+ ...urlRules,
37
+ ...loadingRules,
29
38
  ...hreflangRules,
30
39
  ...structuredDataRules,
40
+ ...structuredDataValueRules,
31
41
  ...contentRules,
32
42
  ...keywordRules,
33
43
  ...responseRules,
34
44
  ];
45
+ /** Rules the AEO audit evaluates against each page; the SEO audit never runs them. */
46
+ export const AEO_PAGE_RULES = aeoRules;
35
47
  /** Metadata for every rule the tool can emit, including rules implemented outside the page-rule loop. */
36
48
  export const ALL_RULES = [
37
49
  ...PAGE_RULES.map((r) => r.meta),
38
50
  ...RENDER_RULES,
39
51
  ...PERFORMANCE_RULES,
40
52
  ...SITE_RULES,
53
+ ...AEO_PAGE_RULES.map((r) => r.meta),
54
+ ...AEO_SITE_RULES,
41
55
  ];
42
56
  export const sortFindings = (findings) => {
43
57
  const rank = { error: 0, warning: 1, info: 2 };
@@ -45,7 +59,7 @@ export const sortFindings = (findings) => {
45
59
  };
46
60
  export const runPageRules = (ctx, options = {}) => {
47
61
  const findings = [];
48
- for (const rule of PAGE_RULES) {
62
+ for (const rule of options.rules ?? PAGE_RULES) {
49
63
  if (options.disable?.has(rule.meta.id)) {
50
64
  continue;
51
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/rules/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/rules/index.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,14 @@
1
1
  import type { ExtractedPage, ResponseInfo } from "../types.js";
2
+ export interface ScopedDirective {
3
+ /** The user agent the directive is for, or null when it is for every crawler. */
4
+ agent: string | null;
5
+ directive: string;
6
+ }
7
+ /**
8
+ * X-Robots-Tag values. "googlebot: nosnippet, noarchive" scopes every directive after the prefix
9
+ * to googlebot, up to the next prefix.
10
+ */
11
+ export declare const parseRobotsHeader: (value: string) => ScopedDirective[];
2
12
  export interface RobotsDirectives {
3
13
  noindex: boolean;
4
14
  nofollow: boolean;
@@ -1,4 +1,108 @@
1
- import { defineRule, GOOGLE_DOCS } from "./rule.js";
1
+ import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
2
+ /** Directives that carry a value after a colon, which is not a user agent prefix. */
3
+ const VALUE_DIRECTIVES = new Set([
4
+ "max-snippet",
5
+ "max-image-preview",
6
+ "max-video-preview",
7
+ "unavailable_after",
8
+ ]);
9
+ /**
10
+ * X-Robots-Tag values. "googlebot: nosnippet, noarchive" scopes every directive after the prefix
11
+ * to googlebot, up to the next prefix.
12
+ */
13
+ export const parseRobotsHeader = (value) => {
14
+ const out = [];
15
+ let agent = null;
16
+ let scoped = new Set();
17
+ for (const piece of value.toLowerCase().split(",")) {
18
+ let directive = piece.trim();
19
+ const match = /^(?<name>[a-z0-9_-]+)\s*:\s*(?<rest>.*)$/u.exec(directive);
20
+ const name = match?.groups?.name;
21
+ if (name && !VALUE_DIRECTIVES.has(name)) {
22
+ agent = name;
23
+ scoped = new Set();
24
+ directive = match.groups?.rest?.trim() ?? "";
25
+ }
26
+ directive = directive.replaceAll(/\s+/gu, "");
27
+ /*
28
+ * Two X-Robots-Tag headers arrive joined with ", ", so "GPTBot: noindex" and a plain "noindex"
29
+ * read as "gptbot: noindex, noindex". A directive that repeats under one agent cannot belong to
30
+ * it twice: it starts a header for every crawler.
31
+ */
32
+ if (agent !== null && scoped.has(directive)) {
33
+ agent = null;
34
+ }
35
+ if (directive) {
36
+ scoped.add(directive);
37
+ out.push({ agent, directive });
38
+ }
39
+ }
40
+ return out;
41
+ };
42
+ /** Robots directives Google or Bing document, which take no value. */
43
+ const KNOWN_DIRECTIVES = new Set([
44
+ "all",
45
+ "index",
46
+ "follow",
47
+ "noindex",
48
+ "nofollow",
49
+ "none",
50
+ "noarchive",
51
+ "nocache",
52
+ "nosnippet",
53
+ "notranslate",
54
+ "noimageindex",
55
+ "indexifembedded",
56
+ "nositelinkssearchbox",
57
+ ]);
58
+ /** Directives Google applies: meta robots and googlebot, and X-Robots-Tag for every crawler or Googlebot. */
59
+ const googleDirectives = (page, response) => {
60
+ const out = [];
61
+ for (const meta of page.answer.robotsMeta) {
62
+ if (meta.name === "robots" || meta.name === "googlebot") {
63
+ for (const d of meta.content.split(",")) {
64
+ const directive = d.trim();
65
+ if (directive) {
66
+ out.push({ source: `<meta name="${meta.name}">`, directive });
67
+ }
68
+ }
69
+ }
70
+ }
71
+ const header = response?.headers["x-robots-tag"];
72
+ if (header) {
73
+ for (const d of parseRobotsHeader(header)) {
74
+ if (d.agent === null || d.agent === "googlebot") {
75
+ out.push({ source: "X-Robots-Tag", directive: d.directive });
76
+ }
77
+ }
78
+ }
79
+ return out;
80
+ };
81
+ /**
82
+ * How a directive Google would not read was meant: "no-index" was meant as noindex, and
83
+ * "noindex nofollow" as two directives. Null for directives Google reads, and for names it does not
84
+ * document, which other crawlers may read.
85
+ */
86
+ const misreadDirective = (directive) => {
87
+ if (KNOWN_DIRECTIVES.has(directive) ||
88
+ VALUE_DIRECTIVES.has(directive.split(":")[0]?.trim() ?? "")) {
89
+ return null;
90
+ }
91
+ const squashed = directive.replaceAll(/[\s_-]+/gu, "");
92
+ if (KNOWN_DIRECTIVES.has(squashed)) {
93
+ return { meant: [squashed], kind: "misspelled" };
94
+ }
95
+ const parts = directive.split(/[\s;]+/u).filter(Boolean);
96
+ return parts.length > 1 && parts.every((p) => KNOWN_DIRECTIVES.has(p))
97
+ ? { meant: parts, kind: "not-comma-separated" }
98
+ : null;
99
+ };
100
+ /** User agents whose robots directives count for search: the same ones the meta tags are read for. */
101
+ const SEARCH_ENGINE_AGENTS = new Set([
102
+ "googlebot",
103
+ "googlebot-news",
104
+ "bingbot",
105
+ ]);
2
106
  /** Combine <meta name="robots"> / googlebot and the X-Robots-Tag header. */
3
107
  export const robotsDirectives = (page, response) => {
4
108
  const directives = [];
@@ -14,14 +118,12 @@ export const robotsDirectives = (page, response) => {
14
118
  }
15
119
  const header = response?.headers["x-robots-tag"];
16
120
  if (header) {
17
- for (const d of header
18
- .toLowerCase()
19
- .split(",")
20
- .map((s) => s.trim())
21
- .filter(Boolean)) {
22
- // "googlebot: noindex" form
23
- directives.push(d.includes(":") ? (d.split(":").pop() ?? d).trim() : d);
24
- sources.push("header");
121
+ // "gptbot: noindex" keeps one AI crawler out; it says nothing about search engines.
122
+ for (const d of parseRobotsHeader(header)) {
123
+ if (d.agent === null || SEARCH_ENGINE_AGENTS.has(d.agent)) {
124
+ directives.push(d.directive);
125
+ sources.push("header");
126
+ }
25
127
  }
26
128
  }
27
129
  const noindex = directives.some((d) => d === "noindex" || d === "none");
@@ -71,5 +173,65 @@ export const indexingRules = [
71
173
  })
72
174
  : null;
73
175
  }),
176
+ defineRule({
177
+ id: "robots.conflicting",
178
+ category: "indexing",
179
+ severity: "info",
180
+ description: "The page's robots directives contradict each other (index and noindex, or follow and nofollow); Google applies the more restrictive one.",
181
+ recommendation: "Keep one directive for indexing and one for following, in one place. A platform or CDN often adds X-Robots-Tag on top of the page's own meta robots; remove the one you did not mean.",
182
+ docs: GOOGLE_DOCS.robotsMeta,
183
+ }, ({ page, response }, make) => {
184
+ const directives = googleDirectives(page, response);
185
+ const where = (names) => [
186
+ ...new Set(directives
187
+ .filter((d) => names.includes(d.directive.toLowerCase()))
188
+ .map((d) => `${d.directive.toLowerCase()} (${d.source})`)),
189
+ ];
190
+ const pairs = [
191
+ { allow: ["index", "all"], deny: ["noindex", "none"], wins: "noindex" },
192
+ {
193
+ allow: ["follow", "all"],
194
+ deny: ["nofollow", "none"],
195
+ wins: "nofollow",
196
+ },
197
+ ];
198
+ return pairs.flatMap(({ allow, deny, wins }) => {
199
+ const allowed = where(allow);
200
+ const denied = where(deny);
201
+ return allowed.length > 0 && denied.length > 0
202
+ ? [
203
+ make(`The page says ${allowed.join(" and ")} but also ${denied.join(" and ")}; Google applies ${wins}.`, { evidence: { allow: allowed, deny: denied, applied: wins } }),
204
+ ]
205
+ : [];
206
+ });
207
+ }),
208
+ defineRule({
209
+ id: "robots.malformed-directive",
210
+ category: "indexing",
211
+ severity: "warning",
212
+ description: 'A robots directive is written in a form Google does not document, such as "no-index" for noindex (warning) or "noindex nofollow" without a comma (info).',
213
+ recommendation: 'Write each directive the way Google documents it, separated by commas: content="noindex, nofollow". A misspelled noindex is ignored, so the page stays indexable.',
214
+ docs: GOOGLE_DOCS.robotsMeta,
215
+ }, ({ page, response }, make) => {
216
+ const misread = googleDirectives(page, response).flatMap((d) => {
217
+ const how = misreadDirective(d.directive.toLowerCase());
218
+ return how ? [{ ...d, ...how }] : [];
219
+ });
220
+ if (misread.length === 0) {
221
+ return null;
222
+ }
223
+ const misspelled = misread.some((m) => m.kind === "misspelled");
224
+ const listed = misread.map((m) => `"${m.directive}" (${m.source}, meant as ${m.meant.join(", ")})`);
225
+ return make(`${misread.length === 1 ? "A robots directive is" : `${misread.length} robots directives are`} not written the way Google documents: ${cap(listed, 5).join("; ")}.`, {
226
+ severity: misspelled ? "warning" : "info",
227
+ evidence: {
228
+ directives: cap(misread.map((m) => ({
229
+ written: m.directive,
230
+ source: m.source,
231
+ meant: m.meant,
232
+ }))),
233
+ },
234
+ });
235
+ }),
74
236
  ];
75
237
  //# sourceMappingURL=indexing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexing.js","sourceRoot":"","sources":["../../../src/core/rules/indexing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"indexing.js","sourceRoot":"","sources":["../../../src/core/rules/indexing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const loadingRules: import("./rule.js").Rule[];
@@ -0,0 +1,138 @@
1
+ import { isHomePage } from "../aeo/page.js";
2
+ import { isLocalHost } from "./canonical.js";
3
+ import { cap, defineRule } from "./rule.js";
4
+ /** Below this size compression saves less than the request costs (Lighthouse's own cut-off). */
5
+ const MIN_COMPRESSIBLE_BYTES = 1400;
6
+ /** An image shown before this many words of text is likely in the first screen. */
7
+ const ABOVE_FOLD_WORDS = 100;
8
+ /** Images narrower than this (by their width attribute) gain little from other sizes. */
9
+ const SRCSET_MIN_WIDTH = 640;
10
+ /** A width or height attribute in pixels; pages also write "800px", which browsers read as 800. */
11
+ const pixels = (value) => {
12
+ const digits = value === null ? undefined : /^\s*\d+(?:\.\d+)?/u.exec(value)?.[0];
13
+ return digits === undefined ? null : Number(digits);
14
+ };
15
+ /** Icons, logos, spacers and tracking pixels: not the image a visitor waits for. */
16
+ const isSmall = (image) => {
17
+ const width = pixels(image.width);
18
+ const height = pixels(image.height);
19
+ return (width !== null && width < 200) || (height !== null && height < 100);
20
+ };
21
+ /** A vector file: in the first screen it is nearly always a logo or an icon. */
22
+ const isVector = (src) => /\.svg(?:[?#]|$)/iu.test(src) || /^data:image\/svg/iu.test(src);
23
+ const isLazy = (image) => image.loading?.trim().toLowerCase() === "lazy";
24
+ /** Vector, animated and inline images, which one file serves at every size. */
25
+ const isFixedFormat = (src) => /^data:/iu.test(src) || /\.(?:svg|gif)(?:[?#]|$)/iu.test(src);
26
+ export const loadingRules = [
27
+ defineRule({
28
+ id: "loading.render-blocking-script",
29
+ category: "performance",
30
+ severity: "info",
31
+ description: 'Scripts in <head> load without async, defer or type="module", so the browser stops building the page until each one has downloaded and run.',
32
+ recommendation: 'Add defer to scripts the page needs once it is parsed, or async to independent ones such as analytics; type="module" scripts are deferred already. A script that must run before anything renders (a consent manager, an A/B test) can stay, but keep it small.',
33
+ docs: "https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources",
34
+ }, ({ page }, make) => {
35
+ const scripts = page.head.blockingScripts;
36
+ return scripts.length > 0
37
+ ? make(`${scripts.length} script(s) in <head> block rendering: ${cap(scripts, 3).join(", ")}.`, {
38
+ selector: "head > script[src]:not([async]):not([defer])",
39
+ evidence: { count: scripts.length, scripts: cap(scripts) },
40
+ })
41
+ : null;
42
+ }),
43
+ defineRule({
44
+ id: "loading.no-compression",
45
+ category: "performance",
46
+ severity: "warning",
47
+ description: "The HTML is sent uncompressed although the request accepted gzip, so every visitor and crawler downloads several times more bytes than needed.",
48
+ recommendation: "Turn on gzip or Brotli compression for text/html (and CSS, JavaScript, JSON, SVG) in the web server, CDN or hosting settings: gzip on in nginx, mod_deflate in Apache, compression() middleware in Express.",
49
+ docs: "https://developer.chrome.com/docs/lighthouse/performance/uses-text-compression",
50
+ }, ({ page, source, response }, make) => {
51
+ if (source !== "url" ||
52
+ !response ||
53
+ response.status !== 200 ||
54
+ isLocalHost(response.finalUrl) ||
55
+ page.htmlBytes < MIN_COMPRESSIBLE_BYTES) {
56
+ return null;
57
+ }
58
+ const encoding = (response.headers["content-encoding"] ?? "")
59
+ .trim()
60
+ .toLowerCase();
61
+ return encoding === "" || encoding === "identity"
62
+ ? make(`The ${Math.round(page.htmlBytes / 1024)} KB of HTML arrived without Content-Encoding.`, { evidence: { bytes: page.htmlBytes, contentEncoding: null } })
63
+ : null;
64
+ }),
65
+ defineRule({
66
+ id: "img.lazy-above-fold",
67
+ category: "images",
68
+ severity: "info",
69
+ description: 'The first large image on the page, likely in the first screen and often the Largest Contentful Paint, has loading="lazy", which delays it (info). An image with both fetchpriority="high" and loading="lazy" contradicts itself (warning).',
70
+ recommendation: 'Remove loading="lazy" from images in the first screen, and give the main one fetchpriority="high". Keep lazy loading for images further down the page. In Next.js, set priority (or preload) on that <Image>.',
71
+ docs: "https://web.dev/articles/browser-level-image-lazy-loading",
72
+ }, ({ page }, make) => {
73
+ const contradicting = page.images.filter((i) => isLazy(i) && i.fetchpriority === "high");
74
+ if (contradicting.length > 0) {
75
+ return make(`${contradicting.length} image(s) have both fetchpriority="high" and loading="lazy".`, {
76
+ severity: "warning",
77
+ selector: 'img[loading="lazy"][fetchpriority="high"]',
78
+ evidence: { images: cap(contradicting.map((i) => i.src)) },
79
+ });
80
+ }
81
+ // An image in a <template> is not on the page until a script stamps it out.
82
+ const first = page.images.find((i) => i.src && i.wordsBefore !== null && !isVector(i.src) && !isSmall(i));
83
+ const wordsBefore = first?.wordsBefore ?? null;
84
+ return first &&
85
+ wordsBefore !== null &&
86
+ isLazy(first) &&
87
+ wordsBefore < ABOVE_FOLD_WORDS
88
+ ? make(`The first image (${first.src}) comes after ${wordsBefore} words of text and has loading="lazy".`, {
89
+ selector: 'img[loading="lazy"]',
90
+ evidence: { src: first.src, wordsBefore },
91
+ })
92
+ : null;
93
+ }),
94
+ defineRule({
95
+ id: "img.srcset-missing",
96
+ category: "images",
97
+ severity: "info",
98
+ description: `Images ${SRCSET_MIN_WIDTH} pixels wide or more (by their width attribute) have no srcset or <picture>, so phones download the full-size file.`,
99
+ recommendation: 'Offer smaller versions with srcset and sizes (srcset="hero-480.jpg 480w, hero-960.jpg 960w" sizes="100vw"), or use the framework\'s image component (next/image, astro:assets, NuxtImg), which generates them.',
100
+ docs: "https://web.dev/articles/serve-responsive-images",
101
+ }, ({ page }, make) => {
102
+ const large = page.images.filter((i) => {
103
+ const width = pixels(i.width);
104
+ return (i.src !== null &&
105
+ !isFixedFormat(i.src) &&
106
+ !i.srcset &&
107
+ !i.inPicture &&
108
+ width !== null &&
109
+ width >= SRCSET_MIN_WIDTH);
110
+ });
111
+ return large.length > 0
112
+ ? make(`${large.length} image(s) at least ${SRCSET_MIN_WIDTH} pixels wide have no srcset.`, {
113
+ evidence: {
114
+ count: large.length,
115
+ examples: cap(large.map((i) => ({ src: i.src, width: i.width }))),
116
+ },
117
+ })
118
+ : null;
119
+ }),
120
+ defineRule({
121
+ id: "favicon.missing",
122
+ category: "document",
123
+ severity: "info",
124
+ description: 'The home page has no <link rel="icon"> (or apple-touch-icon). Google takes the icon it shows next to the site in search results from the home page.',
125
+ recommendation: 'Add <link rel="icon" href="/favicon.svg"> (or a square PNG larger than 48x48 pixels) to the home page\'s <head>, and keep the file crawlable. Google documents only the link, not a /favicon.ico fallback.',
126
+ docs: "https://developers.google.com/search/docs/appearance/favicon-in-search",
127
+ },
128
+ // An error or a bot challenge is not the home page the site serves.
129
+ ({ page, source, response }, make) => source !== "html" &&
130
+ (response?.status ?? 200) < 400 &&
131
+ isHomePage(page.url) &&
132
+ page.head.icons.length === 0
133
+ ? make("The home page links to no icon.", {
134
+ selector: 'head > link[rel="icon"]',
135
+ })
136
+ : null),
137
+ ];
138
+ //# sourceMappingURL=loading.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loading.js","sourceRoot":"","sources":["../../../src/core/rules/loading.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,8 @@ export declare const GOOGLE_DOCS: {
31
31
  readonly hreflang: "https://developers.google.com/search/docs/specialty/international/localized-versions";
32
32
  readonly images: "https://developers.google.com/search/docs/appearance/google-images";
33
33
  readonly links: "https://developers.google.com/search/docs/crawling-indexing/links-crawlable";
34
+ readonly urlStructure: "https://developers.google.com/search/docs/crawling-indexing/url-structure";
35
+ readonly validMetadata: "https://developers.google.com/search/docs/crawling-indexing/valid-page-metadata";
34
36
  readonly structuredData: "https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data";
35
37
  readonly javascript: "https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics";
36
38
  readonly cwv: "https://web.dev/articles/vitals";