@rivi-software/agentic-seo-check 0.0.0-stage → 0.1.1

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -2
  3. package/dist/brand.d.ts +11 -0
  4. package/dist/brand.js +22 -0
  5. package/dist/brand.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +194 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/core/audit.d.ts +72 -0
  10. package/dist/core/audit.js +452 -0
  11. package/dist/core/audit.js.map +1 -0
  12. package/dist/core/concurrency.d.ts +4 -0
  13. package/dist/core/concurrency.js +39 -0
  14. package/dist/core/concurrency.js.map +1 -0
  15. package/dist/core/crawl.d.ts +40 -0
  16. package/dist/core/crawl.js +324 -0
  17. package/dist/core/crawl.js.map +1 -0
  18. package/dist/core/fetch.d.ts +27 -0
  19. package/dist/core/fetch.js +186 -0
  20. package/dist/core/fetch.js.map +1 -0
  21. package/dist/core/html/dom.d.ts +13 -0
  22. package/dist/core/html/dom.js +17 -0
  23. package/dist/core/html/dom.js.map +1 -0
  24. package/dist/core/html/extract.d.ts +10 -0
  25. package/dist/core/html/extract.js +207 -0
  26. package/dist/core/html/extract.js.map +1 -0
  27. package/dist/core/html/structured-data.d.ts +21 -0
  28. package/dist/core/html/structured-data.js +220 -0
  29. package/dist/core/html/structured-data.js.map +1 -0
  30. package/dist/core/html/text.d.ts +23 -0
  31. package/dist/core/html/text.js +152 -0
  32. package/dist/core/html/text.js.map +1 -0
  33. package/dist/core/lighthouse.d.ts +50 -0
  34. package/dist/core/lighthouse.js +216 -0
  35. package/dist/core/lighthouse.js.map +1 -0
  36. package/dist/core/performance-rules.d.ts +3 -0
  37. package/dist/core/performance-rules.js +125 -0
  38. package/dist/core/performance-rules.js.map +1 -0
  39. package/dist/core/render-rules.d.ts +8 -0
  40. package/dist/core/render-rules.js +144 -0
  41. package/dist/core/render-rules.js.map +1 -0
  42. package/dist/core/render.d.ts +22 -0
  43. package/dist/core/render.js +79 -0
  44. package/dist/core/render.js.map +1 -0
  45. package/dist/core/report/markdown.d.ts +2 -0
  46. package/dist/core/report/markdown.js +131 -0
  47. package/dist/core/report/markdown.js.map +1 -0
  48. package/dist/core/robots.d.ts +11 -0
  49. package/dist/core/robots.js +48 -0
  50. package/dist/core/robots.js.map +1 -0
  51. package/dist/core/rules/canonical.d.ts +2 -0
  52. package/dist/core/rules/canonical.js +141 -0
  53. package/dist/core/rules/canonical.js.map +1 -0
  54. package/dist/core/rules/content.d.ts +1 -0
  55. package/dist/core/rules/content.js +55 -0
  56. package/dist/core/rules/content.js.map +1 -0
  57. package/dist/core/rules/document.d.ts +1 -0
  58. package/dist/core/rules/document.js +83 -0
  59. package/dist/core/rules/document.js.map +1 -0
  60. package/dist/core/rules/headings.d.ts +1 -0
  61. package/dist/core/rules/headings.js +81 -0
  62. package/dist/core/rules/headings.js.map +1 -0
  63. package/dist/core/rules/hreflang.d.ts +1 -0
  64. package/dist/core/rules/hreflang.js +100 -0
  65. package/dist/core/rules/hreflang.js.map +1 -0
  66. package/dist/core/rules/images.d.ts +1 -0
  67. package/dist/core/rules/images.js +77 -0
  68. package/dist/core/rules/images.js.map +1 -0
  69. package/dist/core/rules/index.d.ts +14 -0
  70. package/dist/core/rules/index.js +77 -0
  71. package/dist/core/rules/index.js.map +1 -0
  72. package/dist/core/rules/indexing.d.ts +10 -0
  73. package/dist/core/rules/indexing.js +75 -0
  74. package/dist/core/rules/indexing.js.map +1 -0
  75. package/dist/core/rules/keyword.d.ts +1 -0
  76. package/dist/core/rules/keyword.js +111 -0
  77. package/dist/core/rules/keyword.js.map +1 -0
  78. package/dist/core/rules/links.d.ts +1 -0
  79. package/dist/core/rules/links.js +107 -0
  80. package/dist/core/rules/links.js.map +1 -0
  81. package/dist/core/rules/meta.d.ts +1 -0
  82. package/dist/core/rules/meta.js +81 -0
  83. package/dist/core/rules/meta.js.map +1 -0
  84. package/dist/core/rules/response.d.ts +1 -0
  85. package/dist/core/rules/response.js +93 -0
  86. package/dist/core/rules/response.js.map +1 -0
  87. package/dist/core/rules/rule.d.ts +42 -0
  88. package/dist/core/rules/rule.js +51 -0
  89. package/dist/core/rules/rule.js.map +1 -0
  90. package/dist/core/rules/social.d.ts +1 -0
  91. package/dist/core/rules/social.js +115 -0
  92. package/dist/core/rules/social.js.map +1 -0
  93. package/dist/core/rules/structured-data.d.ts +25 -0
  94. package/dist/core/rules/structured-data.js +381 -0
  95. package/dist/core/rules/structured-data.js.map +1 -0
  96. package/dist/core/rules/title.d.ts +1 -0
  97. package/dist/core/rules/title.js +74 -0
  98. package/dist/core/rules/title.js.map +1 -0
  99. package/dist/core/score.d.ts +3 -0
  100. package/dist/core/score.js +17 -0
  101. package/dist/core/score.js.map +1 -0
  102. package/dist/core/site-rules.d.ts +64 -0
  103. package/dist/core/site-rules.js +462 -0
  104. package/dist/core/site-rules.js.map +1 -0
  105. package/dist/core/sitemap.d.ts +13 -0
  106. package/dist/core/sitemap.js +140 -0
  107. package/dist/core/sitemap.js.map +1 -0
  108. package/dist/core/static.d.ts +9 -0
  109. package/dist/core/static.js +281 -0
  110. package/dist/core/static.js.map +1 -0
  111. package/dist/core/summary.d.ts +2 -0
  112. package/dist/core/summary.js +33 -0
  113. package/dist/core/summary.js.map +1 -0
  114. package/dist/core/types.d.ts +296 -0
  115. package/dist/core/types.js +33 -0
  116. package/dist/core/types.js.map +1 -0
  117. package/dist/core/url.d.ts +20 -0
  118. package/dist/core/url.js +191 -0
  119. package/dist/core/url.js.map +1 -0
  120. package/dist/index.d.ts +11 -0
  121. package/dist/index.js +10 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/mcp-bin.d.ts +2 -0
  124. package/dist/mcp-bin.js +10 -0
  125. package/dist/mcp-bin.js.map +1 -0
  126. package/dist/mcp.d.ts +5 -0
  127. package/dist/mcp.js +380 -0
  128. package/dist/mcp.js.map +1 -0
  129. package/dist/version.d.ts +1 -0
  130. package/dist/version.js +6 -0
  131. package/dist/version.js.map +1 -0
  132. package/package.json +107 -3
  133. package/skills/seo-audit/SKILL.md +53 -0
@@ -0,0 +1,50 @@
1
+ import type { PerformanceResult } from "./types.js";
2
+ export declare class LighthouseUnavailableError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export interface LighthouseRunOptions {
6
+ formFactor?: "mobile" | "desktop";
7
+ timeoutMs?: number;
8
+ chromePath?: string;
9
+ userAgent?: string;
10
+ headers?: Record<string, string>;
11
+ }
12
+ export declare const LIGHTHOUSE_INSTALL_HINT = "Install Lighthouse and a Chrome: `pnpm add -D lighthouse chrome-launcher` plus Google Chrome, or `pnpm exec playwright install chromium` (the Playwright Chromium is used automatically when found).";
13
+ interface LhCategory {
14
+ score: number | null;
15
+ auditRefs: {
16
+ id: string;
17
+ weight: number;
18
+ group?: string;
19
+ }[];
20
+ }
21
+ interface LhAudit {
22
+ id: string;
23
+ title: string;
24
+ description: string;
25
+ score: number | null;
26
+ scoreDisplayMode: string;
27
+ numericValue?: number;
28
+ displayValue?: string;
29
+ metricSavings?: Record<string, number | undefined>;
30
+ details?: {
31
+ type?: string;
32
+ overallSavingsMs?: number;
33
+ overallSavingsBytes?: number;
34
+ };
35
+ }
36
+ interface LhrLike {
37
+ lighthouseVersion: string;
38
+ requestedUrl?: string;
39
+ finalDisplayedUrl?: string;
40
+ finalUrl?: string;
41
+ runtimeError?: {
42
+ code: string;
43
+ message: string;
44
+ };
45
+ categories: Record<string, LhCategory | undefined>;
46
+ audits: Record<string, LhAudit | undefined>;
47
+ }
48
+ export declare const summarize: (lhr: LhrLike, requestedUrl: string, formFactor: "mobile" | "desktop") => PerformanceResult;
49
+ export declare const runLighthouse: (url: string, options?: LighthouseRunOptions) => Promise<PerformanceResult>;
50
+ export {};
@@ -0,0 +1,216 @@
1
+ export class LighthouseUnavailableError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "LighthouseUnavailableError";
5
+ }
6
+ }
7
+ export const LIGHTHOUSE_INSTALL_HINT = "Install Lighthouse and a Chrome: `pnpm add -D lighthouse chrome-launcher` plus Google Chrome, or `pnpm exec playwright install chromium` (the Playwright Chromium is used automatically when found).";
8
+ /** Audits we already cover with our own rules; Lighthouse duplicates are suppressed. */
9
+ const COVERED_SEO_AUDITS = new Set([
10
+ "document-title",
11
+ "meta-description",
12
+ "image-alt",
13
+ "canonical",
14
+ "hreflang",
15
+ "link-text",
16
+ "http-status-code",
17
+ "is-crawlable",
18
+ "structured-data",
19
+ "viewport",
20
+ ]);
21
+ /** The metrics whose savings Lighthouse reports in milliseconds. */
22
+ const MS_SAVINGS_METRICS = ["LCP", "FCP", "TBT", "INP", "SI"];
23
+ const stripMarkdownLinks = (text) => text
24
+ .replaceAll(/\[(?<text>[^\]]+)\]\((?<href>https?:[^)]+)\)/gu, "$<text> ($<href>)")
25
+ .trim();
26
+ const percent = (score) => score === null || score === undefined ? null : Math.round(score * 100);
27
+ /** An audit in the performance category as an opportunity, or null when it saves nothing. */
28
+ const opportunityOf = (audit) => {
29
+ const savings = audit.metricSavings ?? {};
30
+ const msValues = MS_SAVINGS_METRICS.flatMap((metric) => {
31
+ const value = savings[metric];
32
+ return value !== undefined && value > 0 ? [value] : [];
33
+ });
34
+ const savingsMs = audit.details?.overallSavingsMs ??
35
+ (msValues.length > 0 ? Math.max(...msValues) : null);
36
+ const savingsBytes = audit.details?.overallSavingsBytes ?? null;
37
+ const hasSavings = (savingsMs ?? 0) > 0 ||
38
+ (savingsBytes ?? 0) > 0 ||
39
+ (savings["CLS"] ?? 0) > 0 ||
40
+ audit.details?.type === "opportunity";
41
+ if (!hasSavings) {
42
+ return null;
43
+ }
44
+ return {
45
+ id: audit.id,
46
+ title: audit.title,
47
+ description: stripMarkdownLinks(audit.description),
48
+ score: audit.score,
49
+ savingsMs: savingsMs ?? null,
50
+ savingsBytes,
51
+ };
52
+ };
53
+ const opportunitiesOf = (lhr) => {
54
+ const perfRefs = lhr.categories["performance"]?.auditRefs ?? [];
55
+ const opportunities = [];
56
+ for (const ref of perfRefs) {
57
+ const audit = lhr.audits[ref.id];
58
+ if (!audit || audit.score === null || audit.score >= 0.9) {
59
+ continue;
60
+ }
61
+ if (ref.group === "metrics" || ref.group === "hidden") {
62
+ continue;
63
+ }
64
+ const opportunity = opportunityOf(audit);
65
+ if (opportunity) {
66
+ opportunities.push(opportunity);
67
+ }
68
+ }
69
+ opportunities.sort((a, b) => (b.savingsMs ?? 0) - (a.savingsMs ?? 0) ||
70
+ (b.savingsBytes ?? 0) - (a.savingsBytes ?? 0));
71
+ return opportunities;
72
+ };
73
+ const failedAudits = (lhr, category, skip) => {
74
+ const refs = lhr.categories[category]?.auditRefs ?? [];
75
+ const out = [];
76
+ for (const ref of refs) {
77
+ const audit = lhr.audits[ref.id];
78
+ if (!audit || skip.has(audit.id)) {
79
+ continue;
80
+ }
81
+ if (!["binary", "numeric", "metricSavings"].includes(audit.scoreDisplayMode)) {
82
+ continue;
83
+ }
84
+ if (audit.score === null || audit.score >= 1) {
85
+ continue;
86
+ }
87
+ out.push({
88
+ id: audit.id,
89
+ title: audit.title,
90
+ description: stripMarkdownLinks(audit.description),
91
+ score: audit.score,
92
+ displayValue: audit.displayValue ?? null,
93
+ });
94
+ }
95
+ return out;
96
+ };
97
+ export const summarize = (lhr, requestedUrl, formFactor) => {
98
+ const numeric = (id) => {
99
+ const value = lhr.audits[id]?.numericValue;
100
+ return value !== undefined && Number.isFinite(value) ? value : null;
101
+ };
102
+ return {
103
+ tool: "lighthouse",
104
+ lighthouseVersion: lhr.lighthouseVersion,
105
+ formFactor,
106
+ requestedUrl,
107
+ finalUrl: lhr.finalDisplayedUrl ?? lhr.finalUrl ?? requestedUrl,
108
+ metrics: {
109
+ performanceScore: percent(lhr.categories["performance"]?.score),
110
+ seoScore: percent(lhr.categories["seo"]?.score),
111
+ agenticBrowsingScore: percent(lhr.categories["agentic-browsing"]?.score),
112
+ lcpMs: numeric("largest-contentful-paint"),
113
+ cls: numeric("cumulative-layout-shift"),
114
+ tbtMs: numeric("total-blocking-time"),
115
+ fcpMs: numeric("first-contentful-paint"),
116
+ speedIndexMs: numeric("speed-index"),
117
+ ttfbMs: numeric("server-response-time"),
118
+ inpMs: numeric("interaction-to-next-paint"),
119
+ },
120
+ opportunities: opportunitiesOf(lhr),
121
+ failedSeoAudits: failedAudits(lhr, "seo", COVERED_SEO_AUDITS),
122
+ agenticBrowsingAudits: failedAudits(lhr, "agentic-browsing", new Set()),
123
+ };
124
+ };
125
+ const playwrightChromiumPath = async () => {
126
+ try {
127
+ const pw = await import("playwright");
128
+ const p = pw.chromium.executablePath();
129
+ const { promises: fs } = await import("node:fs");
130
+ await fs.access(p);
131
+ return p;
132
+ }
133
+ catch {
134
+ return undefined;
135
+ }
136
+ };
137
+ /** Lighthouse and chrome-launcher are optional peers, so they load only when a run asks for them. */
138
+ const loadLighthouse = async () => {
139
+ try {
140
+ const lighthouseModule = await import("lighthouse");
141
+ const chromeLauncher = await import("chrome-launcher");
142
+ return {
143
+ lighthouse: lighthouseModule.default,
144
+ launch: chromeLauncher.launch,
145
+ };
146
+ }
147
+ catch {
148
+ throw new LighthouseUnavailableError(`Lighthouse or chrome-launcher is not installed. ${LIGHTHOUSE_INSTALL_HINT}`);
149
+ }
150
+ };
151
+ export const runLighthouse = async (url, options = {}) => {
152
+ const formFactor = options.formFactor ?? "mobile";
153
+ const { lighthouse, launch } = await loadLighthouse();
154
+ const chromePath = options.chromePath ??
155
+ process.env["CHROME_PATH"] ??
156
+ (await playwrightChromiumPath());
157
+ const launchOptions = {
158
+ chromeFlags: [
159
+ "--headless=new",
160
+ "--no-first-run",
161
+ "--disable-gpu",
162
+ "--no-default-browser-check",
163
+ ],
164
+ logLevel: "silent",
165
+ };
166
+ if (chromePath) {
167
+ launchOptions.chromePath = chromePath;
168
+ }
169
+ let chrome;
170
+ try {
171
+ chrome = await launch(launchOptions);
172
+ }
173
+ catch (error) {
174
+ throw new LighthouseUnavailableError(`Could not launch Chrome: ${error instanceof Error ? error.message : String(error)}. ${LIGHTHOUSE_INSTALL_HINT}`);
175
+ }
176
+ const timeoutMs = options.timeoutMs ?? 120_000;
177
+ let timer;
178
+ try {
179
+ let config;
180
+ if (formFactor === "desktop") {
181
+ const desktop = await import("lighthouse/core/config/desktop-config.js");
182
+ config = desktop.default;
183
+ }
184
+ const flags = {
185
+ port: chrome.port,
186
+ output: "json",
187
+ logLevel: "silent",
188
+ onlyCategories: ["performance", "seo", "agentic-browsing"],
189
+ extraHeaders: options.headers,
190
+ };
191
+ if (options.userAgent) {
192
+ flags.emulatedUserAgent = options.userAgent;
193
+ }
194
+ const run = lighthouse(url, flags, config);
195
+ // oxlint-disable-next-line promise/avoid-new -- setTimeout has no promise form that can be cleared without settling it
196
+ const timeout = new Promise((_resolve, reject) => {
197
+ timer = setTimeout(() => reject(new Error(`Lighthouse timed out after ${timeoutMs} ms`)), timeoutMs);
198
+ });
199
+ const result = await Promise.race([run, timeout]);
200
+ if (!result) {
201
+ throw new Error("Lighthouse returned no result");
202
+ }
203
+ const lhr = result.lhr;
204
+ if (lhr.runtimeError && lhr.runtimeError.code !== "NO_ERROR") {
205
+ throw new Error(`Lighthouse runtime error ${lhr.runtimeError.code}: ${lhr.runtimeError.message}`);
206
+ }
207
+ return summarize(lhr, url, formFactor);
208
+ }
209
+ finally {
210
+ if (timer) {
211
+ clearTimeout(timer);
212
+ }
213
+ chrome.kill();
214
+ }
215
+ };
216
+ //# sourceMappingURL=lighthouse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../../src/core/lighthouse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Finding, PerformanceResult, RuleMeta } from "./types.js";
2
+ export declare const PERFORMANCE_RULES: RuleMeta[];
3
+ export declare const performanceFindings: (result: PerformanceResult) => Finding[];
@@ -0,0 +1,125 @@
1
+ import { finding, GOOGLE_DOCS } from "./rules/rule.js";
2
+ const meta = (id, severity, description, recommendation, docs = GOOGLE_DOCS.cwv) => ({
3
+ id,
4
+ category: "performance",
5
+ severity,
6
+ description,
7
+ recommendation,
8
+ docs,
9
+ requires: "lighthouse",
10
+ });
11
+ export const PERFORMANCE_RULES = [
12
+ meta("perf.lcp-slow", "warning", "Largest Contentful Paint above 2.5 s (poor above 4 s). Lab measurement from Lighthouse.", "Preload the LCP image/font, serve it from a CDN in a modern format, inline critical CSS, and cut server response time."),
13
+ meta("perf.cls-high", "warning", "Cumulative Layout Shift above 0.1 (poor above 0.25).", "Reserve space for images, ads and embeds with width/height or aspect-ratio; avoid inserting content above existing content; use font-display: optional or size-adjusted fallbacks."),
14
+ meta("perf.tbt-high", "warning", "Total Blocking Time above 200 ms (poor above 600 ms). Lab proxy for Interaction to Next Paint.", "Split long JavaScript tasks, defer non-critical scripts, remove unused JavaScript, and move work off the main thread."),
15
+ meta("perf.fcp-slow", "info", "First Contentful Paint above 1.8 s (poor above 3 s).", "Eliminate render-blocking resources, inline critical CSS, and reduce server response time."),
16
+ meta("perf.ttfb-slow", "warning", "Server response time (TTFB) above 800 ms.", "Cache HTML at the edge, avoid slow data fetches during render, and use a nearby region/CDN."),
17
+ meta("perf.score-low", "warning", "Lighthouse performance score below 50.", "Work through the opportunities listed in the performance result, biggest savings first."),
18
+ meta("perf.opportunity", "info", "A Lighthouse opportunity with measurable estimated savings.", "Apply the Lighthouse recommendation described in the finding."),
19
+ meta("lighthouse.seo-audit-failed", "warning", "A Lighthouse SEO audit failed (rendered-DOM checks such as crawlable anchors, robots.txt validity).", "Follow the Lighthouse audit description in the finding.", "https://developer.chrome.com/docs/lighthouse/seo/"),
20
+ meta("lighthouse.agentic-audit-failed", "info", "A Lighthouse agentic-browsing audit failed (llms.txt, accessibility tree quality, WebMCP). Experimental.", "Follow the Lighthouse audit description. These checks reflect how well AI agents can use the site and are not classic ranking factors.", "https://developer.chrome.com/docs/lighthouse/"),
21
+ ];
22
+ const byId = new Map(PERFORMANCE_RULES.map((r) => [r.id, r]));
23
+ const m = (id) => {
24
+ const rule = byId.get(id);
25
+ if (!rule) {
26
+ throw new Error(`Unknown performance rule: ${id}`);
27
+ }
28
+ return rule;
29
+ };
30
+ /** LCP, CLS and TBT against the Core Web Vitals thresholds. */
31
+ const vitalsFindings = (result) => {
32
+ const out = [];
33
+ const add = (f) => out.push(f);
34
+ const { metrics } = result;
35
+ if (metrics.lcpMs !== null && metrics.lcpMs > 2500) {
36
+ add(finding(m("perf.lcp-slow"), `LCP is ${(metrics.lcpMs / 1000).toFixed(1)} s (${metrics.lcpMs > 4000 ? "poor" : "needs improvement"}).`, {
37
+ severity: metrics.lcpMs > 4000 ? "error" : "warning",
38
+ evidence: {
39
+ lcpMs: Math.round(metrics.lcpMs),
40
+ formFactor: result.formFactor,
41
+ },
42
+ }));
43
+ }
44
+ if (metrics.cls !== null && metrics.cls > 0.1) {
45
+ add(finding(m("perf.cls-high"), `CLS is ${metrics.cls.toFixed(3)} (${metrics.cls > 0.25 ? "poor" : "needs improvement"}).`, {
46
+ severity: metrics.cls > 0.25 ? "error" : "warning",
47
+ evidence: { cls: Number(metrics.cls.toFixed(3)) },
48
+ }));
49
+ }
50
+ if (metrics.tbtMs !== null && metrics.tbtMs > 200) {
51
+ add(finding(m("perf.tbt-high"), `TBT is ${Math.round(metrics.tbtMs)} ms (${metrics.tbtMs > 600 ? "poor" : "needs improvement"}).`, {
52
+ severity: metrics.tbtMs > 600 ? "error" : "warning",
53
+ evidence: { tbtMs: Math.round(metrics.tbtMs) },
54
+ }));
55
+ }
56
+ return out;
57
+ };
58
+ /** FCP, server response time and the overall Lighthouse score. */
59
+ const timingFindings = (result) => {
60
+ const out = [];
61
+ const add = (f) => out.push(f);
62
+ const { metrics } = result;
63
+ if (metrics.fcpMs !== null && metrics.fcpMs > 1800) {
64
+ add(finding(m("perf.fcp-slow"), `FCP is ${(metrics.fcpMs / 1000).toFixed(1)} s.`, {
65
+ severity: metrics.fcpMs > 3000 ? "warning" : "info",
66
+ evidence: { fcpMs: Math.round(metrics.fcpMs) },
67
+ }));
68
+ }
69
+ if (metrics.ttfbMs !== null && metrics.ttfbMs > 800) {
70
+ add(finding(m("perf.ttfb-slow"), `Server response time is ${Math.round(metrics.ttfbMs)} ms.`, { evidence: { ttfbMs: Math.round(metrics.ttfbMs) } }));
71
+ }
72
+ if (metrics.performanceScore !== null && metrics.performanceScore < 50) {
73
+ add(finding(m("perf.score-low"), `Lighthouse performance score is ${metrics.performanceScore}/100.`, {
74
+ evidence: {
75
+ performanceScore: metrics.performanceScore,
76
+ formFactor: result.formFactor,
77
+ },
78
+ }));
79
+ }
80
+ return out;
81
+ };
82
+ /** Lighthouse opportunities and failed SEO and agentic-browsing audits. */
83
+ const auditFindings = (result) => {
84
+ const out = [];
85
+ const add = (f) => out.push(f);
86
+ for (const opp of result.opportunities.slice(0, 8)) {
87
+ const savings = [
88
+ opp.savingsMs ? `${Math.round(opp.savingsMs)} ms` : null,
89
+ opp.savingsBytes ? `${Math.round(opp.savingsBytes / 1024)} KiB` : null,
90
+ ]
91
+ .filter(Boolean)
92
+ .join(", ");
93
+ add(finding(m("perf.opportunity"), `${opp.title}${savings ? ` (est. savings ${savings})` : ""}.`, {
94
+ recommendation: opp.description,
95
+ evidence: {
96
+ auditId: opp.id,
97
+ savingsMs: opp.savingsMs,
98
+ savingsBytes: opp.savingsBytes,
99
+ score: opp.score,
100
+ },
101
+ }));
102
+ }
103
+ for (const audit of result.failedSeoAudits) {
104
+ add(finding(m("lighthouse.seo-audit-failed"), `Lighthouse SEO audit "${audit.title}" failed${audit.displayValue ? `: ${audit.displayValue}` : ""}.`, {
105
+ recommendation: audit.description,
106
+ evidence: { auditId: audit.id, score: audit.score },
107
+ }));
108
+ }
109
+ for (const audit of result.agenticBrowsingAudits) {
110
+ add(finding(m("lighthouse.agentic-audit-failed"), `Lighthouse agentic-browsing audit "${audit.title}" failed${audit.displayValue ? `: ${audit.displayValue}` : ""}.`, {
111
+ recommendation: audit.description,
112
+ evidence: { auditId: audit.id, score: audit.score },
113
+ }));
114
+ }
115
+ return out;
116
+ };
117
+ export const performanceFindings = (result) => {
118
+ const url = result.finalUrl || result.requestedUrl;
119
+ return [
120
+ ...vitalsFindings(result),
121
+ ...timingFindings(result),
122
+ ...auditFindings(result),
123
+ ].map((f) => ({ ...f, url }));
124
+ };
125
+ //# sourceMappingURL=performance-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performance-rules.js","sourceRoot":"","sources":["../../src/core/performance-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { ExtractedPage, Finding, RenderComparison, RenderSnapshot, ResponseInfo, RuleMeta } from "./types.js";
2
+ export declare const RENDER_RULES: RuleMeta[];
3
+ export declare const snapshot: (page: ExtractedPage, response?: ResponseInfo) => RenderSnapshot;
4
+ export interface RenderCompareResult {
5
+ comparison: RenderComparison;
6
+ findings: Finding[];
7
+ }
8
+ export declare const compareRender: (raw: ExtractedPage, rendered: ExtractedPage, renderedUrl: string, consoleErrors: string[], rawResponse?: ResponseInfo) => RenderCompareResult;
@@ -0,0 +1,144 @@
1
+ import { robotsDirectives } from "./rules/indexing.js";
2
+ import { finding, GOOGLE_DOCS } from "./rules/rule.js";
3
+ const meta = (id, severity, description, recommendation) => ({
4
+ id,
5
+ category: "rendering",
6
+ severity,
7
+ description,
8
+ recommendation,
9
+ docs: GOOGLE_DOCS.javascript,
10
+ requires: "render",
11
+ });
12
+ const SSR_FIX = "Render this in the server HTML (SSR/SSG/prerender). Googlebot queues JavaScript rendering separately and other crawlers and link previews never run it.";
13
+ export const RENDER_RULES = [
14
+ meta("rendering.title-client-only", "warning", "The <title> only exists after JavaScript runs.", SSR_FIX),
15
+ meta("rendering.title-changed", "info", "JavaScript changes the <title> after load.", "Make the server HTML title match the final title so both agree."),
16
+ meta("rendering.description-client-only", "warning", "The meta description only exists after JavaScript runs.", SSR_FIX),
17
+ meta("rendering.canonical-client-only", "warning", "The canonical link only exists after JavaScript runs.", SSR_FIX),
18
+ meta("rendering.canonical-changed", "warning", "JavaScript changes the canonical URL after load, sending conflicting signals.", "Emit the final canonical in the server HTML and do not modify it client-side."),
19
+ meta("rendering.noindex-client-only", "error", "A noindex directive is injected by JavaScript. Google applies it when it renders the page, which removes the page from the index.", "Remove the client-side noindex (often a framework/head-manager default or an auth guard) or move the decision to the server."),
20
+ meta("rendering.noindex-removed-by-js", "warning", "The server HTML says noindex but JavaScript removes it. Google honours the server noindex and skips rendering, so the page stays out of the index.", "Remove noindex from the server HTML if the page should be indexed."),
21
+ meta("rendering.h1-client-only", "warning", "The <h1> only exists after JavaScript runs.", SSR_FIX),
22
+ meta("rendering.content-client-only", "warning", "Most of the body text only exists after JavaScript runs.", SSR_FIX),
23
+ meta("rendering.links-client-only", "warning", "Most internal links only exist after JavaScript runs, delaying discovery of the linked pages.", `${SSR_FIX} At minimum, render the main navigation and content links server-side.`),
24
+ meta("rendering.structured-data-client-only", "info", "Structured data is only added by JavaScript.", "Google processes JSON-LD injected by JavaScript, but other consumers do not. Prefer emitting it in the server HTML."),
25
+ meta("rendering.console-errors", "info", "The page logged JavaScript errors while rendering.", "Fix runtime errors; a failing script can leave crawlers with an incomplete page."),
26
+ ];
27
+ const byId = new Map(RENDER_RULES.map((r) => [r.id, r]));
28
+ const m = (id) => {
29
+ const rule = byId.get(id);
30
+ if (!rule) {
31
+ throw new Error(`Unknown rendering rule: ${id}`);
32
+ }
33
+ return rule;
34
+ };
35
+ export const snapshot = (page, response) => ({
36
+ title: page.title,
37
+ metaDescription: page.metaDescription,
38
+ canonical: page.canonical,
39
+ robots: robotsDirectives(page, response).directives,
40
+ h1Count: page.h1s.filter(Boolean).length,
41
+ wordCount: page.wordCount,
42
+ internalLinks: page.links.filter((l) => l.internal).length,
43
+ structuredDataCount: page.structuredData.length,
44
+ });
45
+ /** Title, description, canonical and robots: what the head says before and after JavaScript runs. */
46
+ const headFindings = (a, b) => {
47
+ const findings = [];
48
+ const add = (f) => findings.push(f);
49
+ if (!a.title && b.title) {
50
+ add(finding(m("rendering.title-client-only"), `Title "${b.title}" is only present after rendering.`, {
51
+ selector: "head > title",
52
+ evidence: { raw: a.title, rendered: b.title },
53
+ }));
54
+ }
55
+ else if (a.title && b.title && a.title !== b.title) {
56
+ add(finding(m("rendering.title-changed"), "Title differs between server HTML and rendered DOM.", {
57
+ selector: "head > title",
58
+ evidence: { raw: a.title, rendered: b.title },
59
+ }));
60
+ }
61
+ if (!a.metaDescription && b.metaDescription) {
62
+ add(finding(m("rendering.description-client-only"), "Meta description is only present after rendering.", {
63
+ selector: 'head > meta[name="description"]',
64
+ evidence: { rendered: b.metaDescription },
65
+ }));
66
+ }
67
+ if (!a.canonical && b.canonical) {
68
+ add(finding(m("rendering.canonical-client-only"), `Canonical ${b.canonical} is only present after rendering.`, {
69
+ selector: 'head > link[rel="canonical"]',
70
+ evidence: { rendered: b.canonical },
71
+ }));
72
+ }
73
+ else if (a.canonical && b.canonical && a.canonical !== b.canonical) {
74
+ add(finding(m("rendering.canonical-changed"), "Canonical differs between server HTML and rendered DOM.", {
75
+ selector: 'head > link[rel="canonical"]',
76
+ evidence: { raw: a.canonical, rendered: b.canonical },
77
+ }));
78
+ }
79
+ const rawNoindex = a.robots.some((d) => d === "noindex" || d === "none");
80
+ const renderedNoindex = b.robots.some((d) => d === "noindex" || d === "none");
81
+ if (!rawNoindex && renderedNoindex) {
82
+ add(finding(m("rendering.noindex-client-only"), "noindex is added by JavaScript.", {
83
+ selector: 'head > meta[name="robots"]',
84
+ evidence: { raw: a.robots, rendered: b.robots },
85
+ }));
86
+ }
87
+ if (rawNoindex && !renderedNoindex) {
88
+ add(finding(m("rendering.noindex-removed-by-js"), "Server HTML contains noindex; JavaScript removes it.", {
89
+ selector: 'head > meta[name="robots"]',
90
+ evidence: { raw: a.robots, rendered: b.robots },
91
+ }));
92
+ }
93
+ return findings;
94
+ };
95
+ /** H1, words, links, structured data and script errors: what only the rendered body has. */
96
+ const bodyFindings = (a, b, consoleErrors) => {
97
+ const findings = [];
98
+ const add = (f) => findings.push(f);
99
+ if (a.h1Count === 0 && b.h1Count > 0) {
100
+ add(finding(m("rendering.h1-client-only"), "The <h1> is only present after rendering.", {
101
+ selector: "h1",
102
+ evidence: { rawH1Count: a.h1Count, renderedH1Count: b.h1Count },
103
+ }));
104
+ }
105
+ if (b.wordCount - a.wordCount >= 100 && a.wordCount < b.wordCount * 0.5) {
106
+ add(finding(m("rendering.content-client-only"), `Server HTML has ${a.wordCount} words; rendered DOM has ${b.wordCount}.`, { evidence: { rawWords: a.wordCount, renderedWords: b.wordCount } }));
107
+ }
108
+ if (b.internalLinks - a.internalLinks >= 10 &&
109
+ a.internalLinks < b.internalLinks * 0.5) {
110
+ add(finding(m("rendering.links-client-only"), `Server HTML has ${a.internalLinks} internal links; rendered DOM has ${b.internalLinks}.`, {
111
+ evidence: {
112
+ rawLinks: a.internalLinks,
113
+ renderedLinks: b.internalLinks,
114
+ },
115
+ }));
116
+ }
117
+ if (a.structuredDataCount === 0 && b.structuredDataCount > 0) {
118
+ add(finding(m("rendering.structured-data-client-only"), `${b.structuredDataCount} structured data item(s) are only present after rendering.`, { evidence: { renderedCount: b.structuredDataCount } }));
119
+ }
120
+ if (consoleErrors.length) {
121
+ add(finding(m("rendering.console-errors"), `${consoleErrors.length} JavaScript error(s) logged during render.`, { evidence: { errors: consoleErrors.slice(0, 5) } }));
122
+ }
123
+ return findings;
124
+ };
125
+ export const compareRender = (raw, rendered, renderedUrl, consoleErrors, rawResponse) => {
126
+ const a = snapshot(raw, rawResponse);
127
+ const b = snapshot(rendered);
128
+ const url = rawResponse?.finalUrl ?? raw.url;
129
+ const findings = [
130
+ ...headFindings(a, b),
131
+ ...bodyFindings(a, b, consoleErrors),
132
+ ].map((f) => ({ ...f, url }));
133
+ return {
134
+ comparison: {
135
+ tool: "playwright",
136
+ renderedUrl,
137
+ consoleErrors: consoleErrors.slice(0, 20),
138
+ raw: a,
139
+ rendered: b,
140
+ },
141
+ findings,
142
+ };
143
+ };
144
+ //# sourceMappingURL=render-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-rules.js","sourceRoot":"","sources":["../../src/core/render-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export declare class RenderUnavailableError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export interface RenderOptions {
5
+ timeoutMs?: number;
6
+ userAgent?: string;
7
+ headers?: Record<string, string>;
8
+ viewport?: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ /** Wait condition passed to Playwright. */
13
+ waitUntil?: "load" | "domcontentloaded" | "networkidle";
14
+ }
15
+ export interface RenderOutput {
16
+ html: string;
17
+ finalUrl: string;
18
+ consoleErrors: string[];
19
+ }
20
+ export declare const RENDER_INSTALL_HINT = "Install Playwright to enable rendering: `pnpm add -D playwright && pnpm exec playwright install chromium` (or npm/npx equivalents).";
21
+ /** Load the page in headless Chromium and return the rendered DOM. */
22
+ export declare const renderPage: (url: string, options?: RenderOptions) => Promise<RenderOutput>;