@rivi-software/agentic-seo-check 0.4.0 → 0.5.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/README.md +29 -5
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/aeo/audit.d.ts +8 -0
- package/dist/core/aeo/audit.js +101 -17
- package/dist/core/aeo/audit.js.map +1 -1
- package/dist/core/aeo/bots.d.ts +48 -0
- package/dist/core/aeo/bots.js +151 -5
- package/dist/core/aeo/bots.js.map +1 -1
- package/dist/core/aeo/fixes.d.ts +38 -0
- package/dist/core/aeo/fixes.js +111 -0
- package/dist/core/aeo/fixes.js.map +1 -0
- package/dist/core/aeo/site-rules.d.ts +5 -0
- package/dist/core/aeo/site-rules.js +293 -10
- package/dist/core/aeo/site-rules.js.map +1 -1
- package/dist/core/audit.d.ts +8 -0
- package/dist/core/audit.js +144 -25
- package/dist/core/audit.js.map +1 -1
- package/dist/core/crawl.js +43 -4
- package/dist/core/crawl.js.map +1 -1
- package/dist/core/html/answer.d.ts +5 -2
- package/dist/core/html/answer.js +25 -7
- package/dist/core/html/answer.js.map +1 -1
- package/dist/core/html/dom.d.ts +13 -1
- package/dist/core/html/dom.js +30 -2
- package/dist/core/html/dom.js.map +1 -1
- package/dist/core/html/extract.js +122 -30
- package/dist/core/html/extract.js.map +1 -1
- package/dist/core/html/head.js +114 -19
- package/dist/core/html/head.js.map +1 -1
- package/dist/core/html/resources.js +15 -7
- package/dist/core/html/resources.js.map +1 -1
- package/dist/core/html/structured-data.js +21 -5
- package/dist/core/html/structured-data.js.map +1 -1
- package/dist/core/lighthouse.d.ts +2 -5
- package/dist/core/lighthouse.js +290 -5
- package/dist/core/lighthouse.js.map +1 -1
- package/dist/core/performance-fixes.d.ts +25 -0
- package/dist/core/performance-fixes.js +446 -0
- package/dist/core/performance-fixes.js.map +1 -0
- package/dist/core/performance-rules.js +56 -8
- package/dist/core/performance-rules.js.map +1 -1
- package/dist/core/recheck.d.ts +68 -0
- package/dist/core/recheck.js +247 -0
- package/dist/core/recheck.js.map +1 -0
- package/dist/core/render-fixes.d.ts +27 -0
- package/dist/core/render-fixes.js +164 -0
- package/dist/core/render-fixes.js.map +1 -0
- package/dist/core/render-rules.js +60 -21
- package/dist/core/render-rules.js.map +1 -1
- package/dist/core/report/markdown.js +68 -1
- package/dist/core/report/markdown.js.map +1 -1
- package/dist/core/robots.d.ts +5 -0
- package/dist/core/robots.js +8 -0
- package/dist/core/robots.js.map +1 -1
- package/dist/core/rules/aeo.js +339 -25
- package/dist/core/rules/aeo.js.map +1 -1
- package/dist/core/rules/canonical.js +107 -11
- package/dist/core/rules/canonical.js.map +1 -1
- package/dist/core/rules/content.js +65 -7
- package/dist/core/rules/content.js.map +1 -1
- package/dist/core/rules/document.js +73 -8
- package/dist/core/rules/document.js.map +1 -1
- package/dist/core/rules/fix.d.ts +55 -0
- package/dist/core/rules/fix.js +159 -0
- package/dist/core/rules/fix.js.map +1 -0
- package/dist/core/rules/head.js +81 -4
- package/dist/core/rules/head.js.map +1 -1
- package/dist/core/rules/headings.js +92 -9
- package/dist/core/rules/headings.js.map +1 -1
- package/dist/core/rules/hreflang.js +79 -9
- package/dist/core/rules/hreflang.js.map +1 -1
- package/dist/core/rules/images.js +49 -0
- package/dist/core/rules/images.js.map +1 -1
- package/dist/core/rules/index.d.ts +11 -0
- package/dist/core/rules/index.js +21 -1
- package/dist/core/rules/index.js.map +1 -1
- package/dist/core/rules/indexing.js +108 -1
- package/dist/core/rules/indexing.js.map +1 -1
- package/dist/core/rules/keyword.js +91 -1
- package/dist/core/rules/keyword.js.map +1 -1
- package/dist/core/rules/links.js +42 -0
- package/dist/core/rules/links.js.map +1 -1
- package/dist/core/rules/loading.js +54 -1
- package/dist/core/rules/loading.js.map +1 -1
- package/dist/core/rules/meta.js +36 -5
- package/dist/core/rules/meta.js.map +1 -1
- package/dist/core/rules/page-fixes.d.ts +39 -0
- package/dist/core/rules/page-fixes.js +107 -0
- package/dist/core/rules/page-fixes.js.map +1 -0
- package/dist/core/rules/quoted-tags.d.ts +23 -0
- package/dist/core/rules/quoted-tags.js +79 -0
- package/dist/core/rules/quoted-tags.js.map +1 -0
- package/dist/core/rules/response.js +71 -7
- package/dist/core/rules/response.js.map +1 -1
- package/dist/core/rules/rule.d.ts +17 -4
- package/dist/core/rules/rule.js +11 -2
- package/dist/core/rules/rule.js.map +1 -1
- package/dist/core/rules/social.js +101 -10
- package/dist/core/rules/social.js.map +1 -1
- package/dist/core/rules/structured-data-fixes.d.ts +33 -0
- package/dist/core/rules/structured-data-fixes.js +469 -0
- package/dist/core/rules/structured-data-fixes.js.map +1 -0
- package/dist/core/rules/structured-data-values.js +242 -31
- package/dist/core/rules/structured-data-values.js.map +1 -1
- package/dist/core/rules/structured-data.js +171 -4
- package/dist/core/rules/structured-data.js.map +1 -1
- package/dist/core/rules/title.js +39 -5
- package/dist/core/rules/title.js.map +1 -1
- package/dist/core/rules/urls.js +166 -0
- package/dist/core/rules/urls.js.map +1 -1
- package/dist/core/site-fixes.d.ts +111 -0
- package/dist/core/site-fixes.js +650 -0
- package/dist/core/site-fixes.js.map +1 -0
- package/dist/core/site-rules.js +474 -53
- package/dist/core/site-rules.js.map +1 -1
- package/dist/core/sitemap.d.ts +16 -0
- package/dist/core/sitemap.js +34 -0
- package/dist/core/sitemap.js.map +1 -1
- package/dist/core/types.d.ts +223 -3
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +16 -2
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
- package/skills/seo-audit/SKILL.md +22 -5
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
import { includesKeyword, keywordOccurrences, normalizeForComparison, words, } from "../html/text.js";
|
|
2
|
+
import { addHtml, replaceHtml, setAttributeEdit, slot, withContent, } from "./fix.js";
|
|
3
|
+
import { bestBlock, permanentRedirect, rewriteBlock, statusLine, } from "./page-fixes.js";
|
|
2
4
|
import { defineRule } from "./rule.js";
|
|
5
|
+
/** The <title> search engines read rewritten around the keyword, or a new one when the page has none. */
|
|
6
|
+
const titleEdit = (page, keyword) => {
|
|
7
|
+
const index = page.title === null ? -1 : page.titles.indexOf(page.title);
|
|
8
|
+
const tag = page.markup.titles[Math.max(index, 0)];
|
|
9
|
+
const content = slot("title with the keyword");
|
|
10
|
+
const write = `A title of 30-60 characters with "${keyword}" (or a close variant) near the start.`;
|
|
11
|
+
return tag === undefined
|
|
12
|
+
? addHtml("head", `<title>${content}</title>`, write)
|
|
13
|
+
: replaceHtml(tag, withContent(tag, content) ?? `<title>${content}</title>`, {
|
|
14
|
+
at: "head > title",
|
|
15
|
+
write,
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
/** The first <h1>, rewritten around the keyword. */
|
|
19
|
+
const h1Edit = (page, keyword) => {
|
|
20
|
+
const index = page.headings.findIndex((h) => h.level === 1);
|
|
21
|
+
const tag = page.markup.headings[index];
|
|
22
|
+
const content = slot("heading with the keyword");
|
|
23
|
+
const write = `The page's main heading, using "${keyword}" the way a visitor would say it.`;
|
|
24
|
+
return tag === undefined
|
|
25
|
+
? addHtml("main, body", `<h1>${content}</h1>`, write)
|
|
26
|
+
: replaceHtml(tag, withContent(tag, content) ?? `<h1>${content}</h1>`, {
|
|
27
|
+
at: "h1",
|
|
28
|
+
write,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
/** A path segment for the keyword: lowercase words joined with hyphens. */
|
|
32
|
+
const slugOf = (keyword) => words(normalizeForComparison(keyword))
|
|
33
|
+
.join("-")
|
|
34
|
+
.replaceAll(/[^\p{L}\p{N}-]/gu, "");
|
|
35
|
+
/**
|
|
36
|
+
* The page moved to a path with the keyword in it. Renaming an indexed URL costs what search
|
|
37
|
+
* engines know about it, so the edit says to do it only for a new page.
|
|
38
|
+
*/
|
|
39
|
+
const urlEdits = (page, source, status, keyword) => {
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(page.url);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [
|
|
46
|
+
addHtml("body", slot("page at a path with the keyword"), `Serve the page at a path with "${keyword}" in it, such as /${slugOf(keyword)}/, if it is not indexed yet.`),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
const segments = url.pathname.split("/");
|
|
50
|
+
const last = segments.findLastIndex(Boolean);
|
|
51
|
+
const slug = slugOf(keyword);
|
|
52
|
+
// The home page keeps "/": the topic gets a page of its own, linked from it.
|
|
53
|
+
if (last === -1) {
|
|
54
|
+
return [
|
|
55
|
+
addHtml("nav, main", `<a href="/${slug}/">${slot("link text")}</a>`, `A home page's URL stays "/". Give "${keyword}" a page of its own at /${slug}/ and link to it with words a visitor would click.`),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
segments[last] = slug;
|
|
59
|
+
const moved = new URL(segments.join("/"), url).href;
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
in: "http",
|
|
63
|
+
action: "replace",
|
|
64
|
+
at: "status line",
|
|
65
|
+
current: statusLine(status ?? 200),
|
|
66
|
+
code: permanentRedirect(source === "url" ? moved : new URL(moved).pathname),
|
|
67
|
+
write: `Only for a page that is not indexed or linked to yet: serve it at ${new URL(moved).pathname} and redirect the old path there. Leave an indexed URL as it is; a rename resets what search engines know about the page.`,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The paragraph that repeats the keyword most, rewritten whole; when no block holds it, new copy,
|
|
73
|
+
* since words cut from the joined page text would not be in the source as written.
|
|
74
|
+
*/
|
|
75
|
+
const densityFix = (page, keyword, maxPercent) => {
|
|
76
|
+
const block = bestBlock(page.markup.blocks, (b) => keywordOccurrences(b.text, keyword));
|
|
77
|
+
return block
|
|
78
|
+
? rewriteBlock(block, "the same paragraph, the keyword fewer times", `Write "${keyword}" fewer times: use a synonym, a pronoun or nothing, as a reader would write it. Do the same in other paragraphs until it is under ${maxPercent}% of the words.`)
|
|
79
|
+
: addHtml("body", slot("copy with fewer repetitions"), `Rewrite the copy so "${keyword}" is under ${maxPercent}% of the words.`);
|
|
80
|
+
};
|
|
3
81
|
export const keywordRules = [
|
|
4
82
|
defineRule({
|
|
5
83
|
id: "keyword.not-in-title",
|
|
@@ -10,6 +88,7 @@ export const keywordRules = [
|
|
|
10
88
|
requires: "keyword",
|
|
11
89
|
}, ({ page, keyword }, make) => keyword && !includesKeyword(page.title, keyword)
|
|
12
90
|
? make(`Title does not contain "${keyword}".`, {
|
|
91
|
+
fix: [titleEdit(page, keyword)],
|
|
13
92
|
selector: "head > title",
|
|
14
93
|
evidence: { title: page.title, keyword },
|
|
15
94
|
})
|
|
@@ -25,6 +104,7 @@ export const keywordRules = [
|
|
|
25
104
|
page.h1s.length > 0 &&
|
|
26
105
|
!page.h1s.some((h) => includesKeyword(h, keyword))
|
|
27
106
|
? make(`No <h1> contains "${keyword}".`, {
|
|
107
|
+
fix: [h1Edit(page, keyword)],
|
|
28
108
|
selector: "h1",
|
|
29
109
|
evidence: { h1s: page.h1s, keyword },
|
|
30
110
|
})
|
|
@@ -40,6 +120,9 @@ export const keywordRules = [
|
|
|
40
120
|
page.metaDescription &&
|
|
41
121
|
!includesKeyword(page.metaDescription, keyword)
|
|
42
122
|
? make(`Meta description does not contain "${keyword}".`, {
|
|
123
|
+
fix: [
|
|
124
|
+
setAttributeEdit(page.markup.metaDescriptions[Math.max(page.metaDescriptions.indexOf(page.metaDescription), 0)] ?? null, 'head > meta[name="description"]', "content", slot("description with the keyword"), `The description (70-160 characters) with "${keyword}" worked in where it reads naturally; search engines bold the words a searcher typed.`),
|
|
125
|
+
],
|
|
43
126
|
selector: 'head > meta[name="description"]',
|
|
44
127
|
evidence: { description: page.metaDescription, keyword },
|
|
45
128
|
})
|
|
@@ -55,6 +138,9 @@ export const keywordRules = [
|
|
|
55
138
|
page.wordCount > 0 &&
|
|
56
139
|
keywordOccurrences(page.text, keyword) === 0
|
|
57
140
|
? make(`Body text does not contain "${keyword}".`, {
|
|
141
|
+
fix: [
|
|
142
|
+
addHtml("main p, body p", slot("sentence with the keyword"), `A sentence in the first paragraph that uses "${keyword}" naturally, as part of what the page says.`),
|
|
143
|
+
],
|
|
58
144
|
evidence: { keyword, wordCount: page.wordCount },
|
|
59
145
|
})
|
|
60
146
|
: null),
|
|
@@ -65,7 +151,7 @@ export const keywordRules = [
|
|
|
65
151
|
description: "The target keyword does not appear in the URL path.",
|
|
66
152
|
recommendation: "Where the URL is still changeable, use a short slug containing the keyword (hyphen-separated).",
|
|
67
153
|
requires: "keyword",
|
|
68
|
-
}, ({ page, keyword, source }, make) => {
|
|
154
|
+
}, ({ page, keyword, source, response }, make) => {
|
|
69
155
|
if (!keyword || source === "html") {
|
|
70
156
|
return null;
|
|
71
157
|
}
|
|
@@ -80,6 +166,7 @@ export const keywordRules = [
|
|
|
80
166
|
return includesKeyword(slug, keyword)
|
|
81
167
|
? null
|
|
82
168
|
: make(`URL path does not contain "${keyword}".`, {
|
|
169
|
+
fix: urlEdits(page, source, response?.status ?? null, keyword),
|
|
83
170
|
evidence: { path: pathname, keyword },
|
|
84
171
|
});
|
|
85
172
|
}),
|
|
@@ -99,6 +186,9 @@ export const keywordRules = [
|
|
|
99
186
|
const density = ((occurrences * kwWords) / page.wordCount) * 100;
|
|
100
187
|
return density > thresholds.maxKeywordDensityPercent
|
|
101
188
|
? make(`Keyword density is ${density.toFixed(1)}% (${occurrences} occurrences in ${page.wordCount} words).`, {
|
|
189
|
+
fix: [
|
|
190
|
+
densityFix(page, keyword, thresholds.maxKeywordDensityPercent),
|
|
191
|
+
],
|
|
102
192
|
evidence: {
|
|
103
193
|
occurrences,
|
|
104
194
|
wordCount: page.wordCount,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyword.js","sourceRoot":"","sources":["../../../src/core/rules/keyword.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"keyword.js","sourceRoot":"","sources":["../../../src/core/rules/keyword.ts"],"names":[],"mappings":"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"}
|
package/dist/core/rules/links.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { isGenericAnchor } from "../html/text.js";
|
|
2
|
+
import { addHtml, escapeAttribute, replaceHtml, setAttributeEdit, slot, withContent, withoutAttribute, } from "./fix.js";
|
|
3
|
+
import { withAttributeSelector } from "./quoted-tags.js";
|
|
2
4
|
import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
5
|
+
/** The selector of one link, by its href as written. */
|
|
6
|
+
const linkAt = (link) => withAttributeSelector("a", "href", link.href);
|
|
7
|
+
/** The link's start tag with rel="nofollow" taken out, keeping its other rel values. */
|
|
8
|
+
const withoutNofollow = (link) => {
|
|
9
|
+
const rel = (link.rel ?? "")
|
|
10
|
+
.split(/\s+/u)
|
|
11
|
+
.filter((token) => token && token.toLowerCase() !== "nofollow");
|
|
12
|
+
const at = linkAt(link);
|
|
13
|
+
const write = "Unless the target page is kept out of search on purpose; then link to it less, or noindex it.";
|
|
14
|
+
if (rel.length > 0) {
|
|
15
|
+
return setAttributeEdit(link.tag, at, "rel", escapeAttribute(rel.join(" ")), write);
|
|
16
|
+
}
|
|
17
|
+
const code = withoutAttribute(link.tag, "rel");
|
|
18
|
+
return code
|
|
19
|
+
? replaceHtml(link.tag, code, { at, write })
|
|
20
|
+
: setAttributeEdit(null, at, "rel", "", write);
|
|
21
|
+
};
|
|
3
22
|
export const linkRules = [
|
|
4
23
|
defineRule({
|
|
5
24
|
id: "links.no-internal",
|
|
@@ -11,6 +30,9 @@ export const linkRules = [
|
|
|
11
30
|
}, ({ page }, make) => page.links.some((l) => l.internal)
|
|
12
31
|
? null
|
|
13
32
|
: make("Page contains no internal links.", {
|
|
33
|
+
fix: [
|
|
34
|
+
addHtml("body", `<a href="${slot("/path-of-a-related-page/")}">${slot("its name")}</a>`, "A plain <a href> to a page of this site a visitor would want next, with text that names it; the site's navigation or footer with links to the main sections does the same for every page. The href is a path on this site (/pricing/), not a JavaScript click handler."),
|
|
35
|
+
],
|
|
14
36
|
evidence: { totalLinks: page.links.length },
|
|
15
37
|
})),
|
|
16
38
|
defineRule({
|
|
@@ -24,6 +46,7 @@ export const linkRules = [
|
|
|
24
46
|
const empty = page.links.filter((l) => l.url && !l.hasAccessibleName);
|
|
25
47
|
return empty.length
|
|
26
48
|
? make(`${empty.length} link(s) have no accessible text.`, {
|
|
49
|
+
fix: empty.map((l) => setAttributeEdit(l.tag, linkAt(l), "aria-label", slot("where the link goes"), `What the link to ${l.href.slice(0, 120)} leads to, in a few words ("Instagram", "Cart"). Visible text inside the link does the same and also gives search engines anchor text.`)),
|
|
27
50
|
evidence: {
|
|
28
51
|
count: empty.length,
|
|
29
52
|
examples: cap(empty.map((l) => l.href)),
|
|
@@ -40,8 +63,20 @@ export const linkRules = [
|
|
|
40
63
|
docs: GOOGLE_DOCS.links,
|
|
41
64
|
}, ({ page }, make) => {
|
|
42
65
|
const generic = page.links.filter((l) => l.url && l.text && isGenericAnchor(l.text));
|
|
66
|
+
// The whole link as written; when it was cut short, its text, with `at` saying which link.
|
|
67
|
+
const fix = generic.map((l) => {
|
|
68
|
+
const write = `Text that names the destination of ${l.href.slice(0, 120)}, such as "pricing plans" instead of "${l.text.slice(0, 40)}".`;
|
|
69
|
+
const rewritten = withContent(l.outer, slot("what the link leads to"));
|
|
70
|
+
return rewritten
|
|
71
|
+
? replaceHtml(l.outer, rewritten, { at: linkAt(l), write })
|
|
72
|
+
: replaceHtml(l.text, slot("what the link leads to"), {
|
|
73
|
+
at: linkAt(l),
|
|
74
|
+
write,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
43
77
|
return generic.length
|
|
44
78
|
? make(`${generic.length} link(s) use generic anchor text.`, {
|
|
79
|
+
fix,
|
|
45
80
|
evidence: {
|
|
46
81
|
count: generic.length,
|
|
47
82
|
examples: cap(generic.map((l) => ({ text: l.text, href: l.href }))),
|
|
@@ -60,6 +95,7 @@ export const linkRules = [
|
|
|
60
95
|
const bad = page.links.filter((l) => l.href === "" || l.href === "#" || /^javascript:/iu.test(l.href));
|
|
61
96
|
return bad.length
|
|
62
97
|
? make(`${bad.length} anchor(s) have an uncrawlable href.`, {
|
|
98
|
+
fix: bad.map((l) => setAttributeEdit(l.tag, linkAt(l), "href", slot("URL the link opens"), `The URL of the page this link opens, even when JavaScript handles the click. If it runs an action instead of opening a page${l.text ? ` ("${l.text.slice(0, 40)}")` : ""}, make it a <button type="button"> and its </a> a </button>.`)),
|
|
63
99
|
evidence: {
|
|
64
100
|
count: bad.length,
|
|
65
101
|
examples: cap(bad.map((l) => ({ href: l.href, text: l.text }))),
|
|
@@ -79,8 +115,13 @@ export const linkRules = [
|
|
|
79
115
|
return null;
|
|
80
116
|
}
|
|
81
117
|
const insecure = page.links.filter((l) => l.internal && l.url?.startsWith("http://"));
|
|
118
|
+
// The href as written with https; one that is not written as http:// gets the full URL.
|
|
119
|
+
const fix = insecure.map((l) => setAttributeEdit(l.tag, linkAt(l), "href", escapeAttribute(/^http:/iu.test(l.href)
|
|
120
|
+
? l.href.replace(/^http:/iu, "https:")
|
|
121
|
+
: (l.url ?? l.href).replace(/^http:/iu, "https:"))));
|
|
82
122
|
return insecure.length
|
|
83
123
|
? make(`${insecure.length} internal link(s) point to http:// URLs.`, {
|
|
124
|
+
fix,
|
|
84
125
|
evidence: {
|
|
85
126
|
count: insecure.length,
|
|
86
127
|
examples: cap(insecure.map((l) => l.url)),
|
|
@@ -99,6 +140,7 @@ export const linkRules = [
|
|
|
99
140
|
const nf = page.links.filter((l) => l.internal && l.nofollow);
|
|
100
141
|
return nf.length
|
|
101
142
|
? make(`${nf.length} internal link(s) are rel="nofollow".`, {
|
|
143
|
+
fix: nf.map((l) => withoutNofollow(l)),
|
|
102
144
|
evidence: { count: nf.length, examples: cap(nf.map((l) => l.url)) },
|
|
103
145
|
})
|
|
104
146
|
: null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/core/rules/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/core/rules/links.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { isHomePage } from "../aeo/page.js";
|
|
2
2
|
import { isLocalHost } from "./canonical.js";
|
|
3
|
+
import { addEdit, addHtml, escapeAttribute, replaceHtml, setAttributeEdit, slot, withAttribute, withoutAttribute, } from "./fix.js";
|
|
3
4
|
import { cap, defineRule } from "./rule.js";
|
|
5
|
+
/** A selector for an image by its src, for an edit that cannot quote the tag. */
|
|
6
|
+
const imageAt = (image) => image.src ? `img[src="${image.src.replaceAll('"', String.raw `\"`)}"]` : "img";
|
|
7
|
+
/**
|
|
8
|
+
* The edit that takes loading="lazy" off an image and, for the main image, asks the browser to
|
|
9
|
+
* fetch it first.
|
|
10
|
+
*/
|
|
11
|
+
const eagerEdit = (image, priority) => {
|
|
12
|
+
const withoutLazy = withoutAttribute(image.tag, "loading");
|
|
13
|
+
const rewritten = withoutLazy && priority
|
|
14
|
+
? withAttribute(withoutLazy, "fetchpriority", "high")
|
|
15
|
+
: withoutLazy;
|
|
16
|
+
if (rewritten) {
|
|
17
|
+
return replaceHtml(image.tag, rewritten, { at: imageAt(image) });
|
|
18
|
+
}
|
|
19
|
+
return addHtml(imageAt(image), priority ? 'fetchpriority="high"' : 'loading="eager"', priority
|
|
20
|
+
? 'Set this attribute and delete loading="lazy" from the image.'
|
|
21
|
+
: 'This replaces loading="lazy" on the image.');
|
|
22
|
+
};
|
|
4
23
|
/** Below this size compression saves less than the request costs (Lighthouse's own cut-off). */
|
|
5
24
|
const MIN_COMPRESSIBLE_BYTES = 1400;
|
|
6
25
|
/** An image shown before this many words of text is likely in the first screen. */
|
|
@@ -23,6 +42,26 @@ const isVector = (src) => /\.svg(?:[?#]|$)/iu.test(src) || /^data:image\/svg/iu.
|
|
|
23
42
|
const isLazy = (image) => image.loading?.trim().toLowerCase() === "lazy";
|
|
24
43
|
/** Vector, animated and inline images, which one file serves at every size. */
|
|
25
44
|
const isFixedFormat = (src) => /^data:/iu.test(src) || /\.(?:svg|gif)(?:[?#]|$)/iu.test(src);
|
|
45
|
+
/** defer on a script in <head>, which the page's tags do not quote, so the attribute is added at it. */
|
|
46
|
+
/** `defer` on a blocking script, written into its tag as the page writes it. */
|
|
47
|
+
const deferEdit = (src, tag) => setAttributeEdit(tag ?? null, `head > script[src="${src.replaceAll('"', String.raw `\"`)}"]`, "defer", true, "Only when no inline script further down calls this one before the page has loaded (a jQuery plugin set-up, for instance), and it is not a consent manager or A/B test that must run before anything renders; async suits independent scripts such as analytics.");
|
|
48
|
+
/**
|
|
49
|
+
* srcset and sizes for a large image: the file it has at its own width, plus smaller copies only
|
|
50
|
+
* the author can make.
|
|
51
|
+
*/
|
|
52
|
+
const srcsetEdit = (image) => {
|
|
53
|
+
const own = `${escapeAttribute(image.src ?? "")} ${pixels(image.width)}w`;
|
|
54
|
+
const srcset = `${slot("smaller copies, e.g. /img-480.jpg 480w, /img-960.jpg 960w")}, ${own}`;
|
|
55
|
+
const sizes = slot("display width, e.g. 100vw");
|
|
56
|
+
const write = "srcset: smaller copies of the image (the framework's image component or a CDN image service makes them) with their pixel widths; sizes: how wide the image shows, such as 100vw or (min-width: 800px) 50vw, 100vw.";
|
|
57
|
+
const withSrcset = withAttribute(image.tag, "srcset", srcset);
|
|
58
|
+
const rewritten = withSrcset
|
|
59
|
+
? withAttribute(withSrcset, "sizes", sizes)
|
|
60
|
+
: null;
|
|
61
|
+
return rewritten
|
|
62
|
+
? replaceHtml(image.tag, rewritten, { at: imageAt(image), write })
|
|
63
|
+
: addHtml(imageAt(image), `srcset="${srcset}" sizes="${sizes}"`, write);
|
|
64
|
+
};
|
|
26
65
|
export const loadingRules = [
|
|
27
66
|
defineRule({
|
|
28
67
|
id: "loading.render-blocking-script",
|
|
@@ -35,6 +74,7 @@ export const loadingRules = [
|
|
|
35
74
|
const scripts = page.head.blockingScripts;
|
|
36
75
|
return scripts.length > 0
|
|
37
76
|
? make(`${scripts.length} script(s) in <head> block rendering: ${cap(scripts, 3).join(", ")}.`, {
|
|
77
|
+
fix: scripts.map((src, i) => deferEdit(src, page.head.sources.blockingScripts[i])),
|
|
38
78
|
selector: "head > script[src]:not([async]):not([defer])",
|
|
39
79
|
evidence: { count: scripts.length, scripts: cap(scripts) },
|
|
40
80
|
})
|
|
@@ -59,7 +99,14 @@ export const loadingRules = [
|
|
|
59
99
|
.trim()
|
|
60
100
|
.toLowerCase();
|
|
61
101
|
return encoding === "" || encoding === "identity"
|
|
62
|
-
? make(`The ${Math.round(page.htmlBytes / 1024)} KB of HTML arrived without Content-Encoding.`, {
|
|
102
|
+
? make(`The ${Math.round(page.htmlBytes / 1024)} KB of HTML arrived without Content-Encoding.`, {
|
|
103
|
+
fix: [
|
|
104
|
+
addEdit("http", "response headers", "Content-Encoding: gzip", {
|
|
105
|
+
write: "Turn on compression in the web server or CDN so it sends this header with a gzip (or br) body: `gzip on; gzip_types text/html text/css application/javascript application/json image/svg+xml;` in nginx, mod_deflate in Apache, compression() middleware in Express, or the CDN's compression setting. Never set the header by hand: the body must be compressed to match it.",
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
evidence: { bytes: page.htmlBytes, contentEncoding: null },
|
|
109
|
+
})
|
|
63
110
|
: null;
|
|
64
111
|
}),
|
|
65
112
|
defineRule({
|
|
@@ -74,6 +121,7 @@ export const loadingRules = [
|
|
|
74
121
|
if (contradicting.length > 0) {
|
|
75
122
|
return make(`${contradicting.length} image(s) have both fetchpriority="high" and loading="lazy".`, {
|
|
76
123
|
severity: "warning",
|
|
124
|
+
fix: contradicting.map((image) => eagerEdit(image, false)),
|
|
77
125
|
selector: 'img[loading="lazy"][fetchpriority="high"]',
|
|
78
126
|
evidence: { images: cap(contradicting.map((i) => i.src)) },
|
|
79
127
|
});
|
|
@@ -86,6 +134,7 @@ export const loadingRules = [
|
|
|
86
134
|
isLazy(first) &&
|
|
87
135
|
wordsBefore < ABOVE_FOLD_WORDS
|
|
88
136
|
? make(`The first image (${first.src}) comes after ${wordsBefore} words of text and has loading="lazy".`, {
|
|
137
|
+
fix: [eagerEdit(first, true)],
|
|
89
138
|
selector: 'img[loading="lazy"]',
|
|
90
139
|
evidence: { src: first.src, wordsBefore },
|
|
91
140
|
})
|
|
@@ -110,6 +159,7 @@ export const loadingRules = [
|
|
|
110
159
|
});
|
|
111
160
|
return large.length > 0
|
|
112
161
|
? make(`${large.length} image(s) at least ${SRCSET_MIN_WIDTH} pixels wide have no srcset.`, {
|
|
162
|
+
fix: large.map((image) => srcsetEdit(image)),
|
|
113
163
|
evidence: {
|
|
114
164
|
count: large.length,
|
|
115
165
|
examples: cap(large.map((i) => ({ src: i.src, width: i.width }))),
|
|
@@ -131,6 +181,9 @@ export const loadingRules = [
|
|
|
131
181
|
isHomePage(page.url) &&
|
|
132
182
|
page.head.icons.length === 0
|
|
133
183
|
? make("The home page links to no icon.", {
|
|
184
|
+
fix: [
|
|
185
|
+
addHtml("head", `<link rel="icon" href="${slot("/favicon.svg")}">`, "The path of the site's icon: an SVG, or a square PNG larger than 48x48 pixels. Add the file if the site has none, and keep it crawlable."),
|
|
186
|
+
],
|
|
134
187
|
selector: 'head > link[rel="icon"]',
|
|
135
188
|
})
|
|
136
189
|
: null),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loading.js","sourceRoot":"","sources":["../../../src/core/rules/loading.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"loading.js","sourceRoot":"","sources":["../../../src/core/rules/loading.ts"],"names":[],"mappings":"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"}
|
package/dist/core/rules/meta.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { normalizeForComparison } from "../html/text.js";
|
|
2
|
+
import { addHtml, removeHtml, setAttributeEdit, slot } from "./fix.js";
|
|
2
3
|
import { defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
3
4
|
const SELECTOR = 'head > meta[name="description"]';
|
|
5
|
+
/** The meta description search engines read: the first one with text. */
|
|
6
|
+
const descriptionIndex = (page) => page.metaDescription === null
|
|
7
|
+
? -1
|
|
8
|
+
: page.metaDescriptions.indexOf(page.metaDescription);
|
|
9
|
+
/** The edit that sets the page's meta description: rewrites the tag it has, or adds one. */
|
|
10
|
+
const writeDescription = (page, hint, write) => {
|
|
11
|
+
const tag = page.markup.metaDescriptions[Math.max(descriptionIndex(page), 0)];
|
|
12
|
+
return tag === undefined
|
|
13
|
+
? addHtml("head", `<meta name="description" content="${slot(hint)}">`, write)
|
|
14
|
+
: setAttributeEdit(tag, SELECTOR, "content", slot(hint), write);
|
|
15
|
+
};
|
|
16
|
+
const DESCRIBE = "A summary of this page in 70-160 characters that tells a searcher what they get by clicking.";
|
|
4
17
|
export const metaRules = [
|
|
5
18
|
defineRule({
|
|
6
19
|
id: "meta-description.missing",
|
|
@@ -11,7 +24,10 @@ export const metaRules = [
|
|
|
11
24
|
docs: GOOGLE_DOCS.snippets,
|
|
12
25
|
}, ({ page }, make) => page.metaDescription
|
|
13
26
|
? null
|
|
14
|
-
: make("Page has no meta description.", {
|
|
27
|
+
: make("Page has no meta description.", {
|
|
28
|
+
fix: [writeDescription(page, "page description", DESCRIBE)],
|
|
29
|
+
selector: SELECTOR,
|
|
30
|
+
})),
|
|
15
31
|
defineRule({
|
|
16
32
|
id: "meta-description.multiple",
|
|
17
33
|
category: "meta",
|
|
@@ -19,12 +35,18 @@ export const metaRules = [
|
|
|
19
35
|
description: "More than one meta description is present.",
|
|
20
36
|
recommendation: "Keep a single meta description; remove or merge the others.",
|
|
21
37
|
docs: GOOGLE_DOCS.snippets,
|
|
22
|
-
}, ({ page }, make) =>
|
|
23
|
-
|
|
38
|
+
}, ({ page }, make) => {
|
|
39
|
+
if (page.metaDescriptions.length <= 1) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Keep the description search engines read now; remove the others.
|
|
43
|
+
const keep = Math.max(descriptionIndex(page), 0);
|
|
44
|
+
return make(`Page has ${page.metaDescriptions.length} meta description tags.`, {
|
|
45
|
+
fix: page.markup.metaDescriptions.flatMap((tag, i) => i === keep ? [] : [removeHtml(tag, SELECTOR)]),
|
|
24
46
|
selector: SELECTOR,
|
|
25
47
|
evidence: { descriptions: page.metaDescriptions },
|
|
26
|
-
})
|
|
27
|
-
|
|
48
|
+
});
|
|
49
|
+
}),
|
|
28
50
|
defineRule({
|
|
29
51
|
id: "meta-description.too-long",
|
|
30
52
|
category: "meta",
|
|
@@ -35,6 +57,9 @@ export const metaRules = [
|
|
|
35
57
|
}, ({ page, thresholds }, make) => page.metaDescription &&
|
|
36
58
|
page.metaDescription.length > thresholds.descriptionMaxChars
|
|
37
59
|
? make(`Meta description is ${page.metaDescription.length} characters (max ${thresholds.descriptionMaxChars}).`, {
|
|
60
|
+
fix: [
|
|
61
|
+
writeDescription(page, "shorter description", `The current description in at most ${thresholds.descriptionMaxChars} characters, the key message first.`),
|
|
62
|
+
],
|
|
38
63
|
selector: SELECTOR,
|
|
39
64
|
evidence: {
|
|
40
65
|
description: page.metaDescription,
|
|
@@ -53,6 +78,9 @@ export const metaRules = [
|
|
|
53
78
|
}, ({ page, thresholds }, make) => page.metaDescription &&
|
|
54
79
|
page.metaDescription.length < thresholds.descriptionMinChars
|
|
55
80
|
? make(`Meta description is only ${page.metaDescription.length} characters (recommended at least ${thresholds.descriptionMinChars}).`, {
|
|
81
|
+
fix: [
|
|
82
|
+
writeDescription(page, "longer description", `The current description grown to ${thresholds.descriptionMinChars}-${thresholds.descriptionMaxChars} characters with what the page offers and why it is worth the click.`),
|
|
83
|
+
],
|
|
56
84
|
selector: SELECTOR,
|
|
57
85
|
evidence: {
|
|
58
86
|
description: page.metaDescription,
|
|
@@ -73,6 +101,9 @@ export const metaRules = [
|
|
|
73
101
|
normalizeForComparison(page.metaDescription) ===
|
|
74
102
|
normalizeForComparison(page.title)
|
|
75
103
|
? make("Meta description repeats the title verbatim.", {
|
|
104
|
+
fix: [
|
|
105
|
+
writeDescription(page, "page description", "A description that adds to the title: what the page covers or offers, in 70-160 characters."),
|
|
106
|
+
],
|
|
76
107
|
selector: SELECTOR,
|
|
77
108
|
evidence: {
|
|
78
109
|
title: page.title,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/core/rules/meta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/core/rules/meta.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { JsonValue } from "../html/structured-data.js";
|
|
2
|
+
import type { ExtractedPage, FixEdit, PageSource, TextBlock } from "../types.js";
|
|
3
|
+
/** An HTTP/1.1 status line: "HTTP/1.1 301 Moved Permanently". */
|
|
4
|
+
export declare const statusLine: (status: number) => string;
|
|
5
|
+
/** The status and Location of a permanent redirect to `location`. */
|
|
6
|
+
export declare const permanentRedirect: (location: string) => string;
|
|
7
|
+
/** Where a JSON-LD object sits, for an edit's `at`: its script, its type and its path inside the script. */
|
|
8
|
+
export declare const jsonLdAt: (type: string, path?: string) => string;
|
|
9
|
+
/**
|
|
10
|
+
* `"key": value` as the script writes it (any spacing around the colon), or null when it is not
|
|
11
|
+
* there verbatim, for instance because the script escapes a character the value holds.
|
|
12
|
+
*/
|
|
13
|
+
export declare const jsonProperty: (scriptText: string | undefined, key: string, value: JsonValue) => string | null;
|
|
14
|
+
/** A JSON value for a JSON-LD fix, safe inside a <script>: "<" is escaped so "</script>" cannot end it. */
|
|
15
|
+
export declare const json: (value: JsonValue) => string;
|
|
16
|
+
/**
|
|
17
|
+
* The origin to build absolute URLs from: the page's own, or a slot when the page is on a
|
|
18
|
+
* development host or is HTML without a URL, where only the author knows the production origin.
|
|
19
|
+
*/
|
|
20
|
+
export declare const productionOrigin: (page: ExtractedPage, source: PageSource) => string;
|
|
21
|
+
/** What a production-origin slot needs, for `write`. */
|
|
22
|
+
export declare const ORIGIN_SLOT_WRITE = "The site's production origin (scheme and host, such as https://www.example.com), from the site URL setting rather than the development server.";
|
|
23
|
+
/** Whether a `productionOrigin` is a slot the author fills in. */
|
|
24
|
+
export declare const isSlot: (text: string) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* `raw`, a URL as an attribute writes it, without the query parameters `drop` selects (and a
|
|
27
|
+
* `;jsessionid=` path parameter when `drop` selects "jsessionid"), written the way `raw` was:
|
|
28
|
+
* absolute stays absolute, a path stays a path.
|
|
29
|
+
*/
|
|
30
|
+
export declare const withoutParameters: (raw: string, base: string, drop: (name: string) => boolean) => string;
|
|
31
|
+
/** Sentences of visible text that end in a full stop, question or exclamation mark. */
|
|
32
|
+
export declare const sentences: (text: string) => string[];
|
|
33
|
+
/**
|
|
34
|
+
* The edit that rewrites one text block: the element as written, with a slot for its content. A
|
|
35
|
+
* block quoted cut short is rebuilt from its start tag, so the code is still whole.
|
|
36
|
+
*/
|
|
37
|
+
export declare const rewriteBlock: (block: TextBlock, hint: string, write: string) => FixEdit;
|
|
38
|
+
/** The block of `blocks` that scores highest, or null when none scores above 0. */
|
|
39
|
+
export declare const bestBlock: (blocks: TextBlock[], score: (block: TextBlock) => number) => TextBlock | null;
|