diff --git a/src/server/lib/audit/page-analyzer.test.ts b/src/server/lib/audit/page-analyzer.test.ts index 557a5360d..31f632e0c 100644 --- a/src/server/lib/audit/page-analyzer.test.ts +++ b/src/server/lib/audit/page-analyzer.test.ts @@ -207,6 +207,26 @@ describe("analyzeHtml parity with the DOM reference", () => { }); }); +describe("analyzeHtml link resolution", () => { + const linkTargets = (html: string) => + analyzeHtml(html, PAGE_URL, 200, 0).links.map((link) => link.targetUrl); + + it("resolves relative links against ", () => { + expect( + linkTargets( + ` + Guide`, + ), + ).toEqual(["https://example.com/en/guide"]); + }); + + it("resolves relative links against the page URL without a ", () => { + expect( + linkTargets(`Guide`), + ).toEqual(["https://example.com/blog/guide"]); + }); +}); + describe("analyzeHtml extraction caps", () => { it("caps links and images per page", () => { const links = Array.from( diff --git a/src/server/lib/audit/page-analyzer.ts b/src/server/lib/audit/page-analyzer.ts index 47db8ce55..ca9897cd3 100644 --- a/src/server/lib/audit/page-analyzer.ts +++ b/src/server/lib/audit/page-analyzer.ts @@ -66,6 +66,10 @@ export function analyzeHtml( let ogImage: string | null = null; let hasStructuredData = false; const hreflangTags: string[] = []; + // Resolution base for relative link targets. HTML lets a document override + // it with , which sits in and so is always parsed before + // the links it governs; the first with an href wins. + let linkBase: string | null = null; const h1s: string[] = []; const headingOrder: number[] = []; @@ -113,7 +117,7 @@ export function analyzeHtml( const { href, rel, text } = openAnchor; openAnchor = null; if (linksByTarget.size >= MAX_EXTRACTED_LINKS) return; - const resolved = normalizeUrl(href, pageUrl); + const resolved = normalizeUrl(href, linkBase ?? pageUrl); if (!resolved || linksByTarget.has(resolved)) return; const anchor = text .join("") @@ -157,6 +161,11 @@ export function analyzeHtml( case "link": handleLinkTag(attribs); break; + case "base": + if (linkBase === null && attribs["href"]) { + linkBase = normalizeUrl(attribs["href"], pageUrl); + } + break; case "img": if (images.length < MAX_EXTRACTED_IMAGES) { images.push({