From 90f09e6b0f734f60ee9c6606e3de09f134ebe5a2 Mon Sep 17 00:00:00 2001 From: Thomas Mendy Date: Sun, 2 Aug 2026 14:21:15 +0200 Subject: [PATCH] feat(audit): flag indexable pages with no structured data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Site audits had no signal at all for structured data, even though the crawler already records `hasStructuredData` per page. Pages that could be earning rich results — and that AI search engines have to interpret from prose alone — were silently passing the audit. Adds a `missing-structured-data` info-level issue, reported for indexable HTML pages whose markup carries no JSON-LD, Microdata, or RDFa. Noindex pages are skipped: they cannot earn a rich result, so flagging them would be noise. Co-Authored-By: Claude Opus 5 --- .../lib/audit/issues/page-reporters.test.ts | 38 ++++++++++++++++++- src/server/lib/audit/issues/page-reporters.ts | 6 +++ src/shared/audit-issues.ts | 8 ++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/server/lib/audit/issues/page-reporters.test.ts b/src/server/lib/audit/issues/page-reporters.test.ts index 13f76ca6..45677c4a 100644 --- a/src/server/lib/audit/issues/page-reporters.test.ts +++ b/src/server/lib/audit/issues/page-reporters.test.ts @@ -45,7 +45,7 @@ function makePage(overrides: Partial): CrawledPageResult { imagesMissingAlt: 0, images: [], links: [HEALTHY_LINK], - hasStructuredData: false, + hasStructuredData: true, hreflangTags: [], isIndexable: true, responseTimeMs: 200, @@ -137,6 +137,42 @@ describe("runPageReporters", () => { expect(issueTypes(nonHtml)).toEqual([]); }); + it("reports missing-structured-data on an indexable page without markup", () => { + expect(issueTypes(makePage({ hasStructuredData: false }))).toEqual([ + "missing-structured-data", + ]); + }); + + it("does not report missing-structured-data when markup is present", () => { + expect(issueTypes(makePage({ hasStructuredData: true }))).not.toContain( + "missing-structured-data", + ); + }); + + it("does not report missing-structured-data on a noindex page", () => { + // The page is explicitly kept out of the index, so rich results are moot. + expect( + issueTypes(makePage({ hasStructuredData: false, isIndexable: false })), + ).not.toContain("missing-structured-data"); + }); + + it("does not report missing-structured-data on non-HTML responses", () => { + expect( + issueTypes( + makePage({ + isHtml: false, + hasStructuredData: false, + title: "", + metaDescription: "", + h1Count: 0, + headingOrder: [], + wordCount: 0, + contentHash: null, + }), + ), + ).not.toContain("missing-structured-data"); + }); + it("still checks empty-shell HTML pages", () => { const shell = makePage({ isHtml: true, diff --git a/src/server/lib/audit/issues/page-reporters.ts b/src/server/lib/audit/issues/page-reporters.ts index d166739d..998bbb29 100644 --- a/src/server/lib/audit/issues/page-reporters.ts +++ b/src/server/lib/audit/issues/page-reporters.ts @@ -142,6 +142,12 @@ export function runPageReporters(page: CrawledPageResult): DetectedIssue[] { }); } + // Rich-result eligibility. Only meaningful for pages that are allowed in the + // index — a noindex page can't earn a rich result in the first place. + if (page.isIndexable && !page.hasStructuredData) { + report("missing-structured-data"); + } + // Structure if (page.isIndexable && page.links.length === 0) { report("no-outgoing-links"); diff --git a/src/shared/audit-issues.ts b/src/shared/audit-issues.ts index baddfa7f..1e928820 100644 --- a/src/shared/audit-issues.ts +++ b/src/shared/audit-issues.ts @@ -224,6 +224,14 @@ export const AUDIT_ISSUE_TYPES = { howToFix: "If this page should rank on its own, set its canonical to itself. Otherwise no action is needed.", }, + "missing-structured-data": { + severity: "info", + title: "No structured data", + explanation: + "The page carries no structured data (JSON-LD, Microdata, or RDFa). Structured data is what makes a page eligible for rich results — review stars, prices, FAQs, breadcrumbs — and it gives AI search engines an unambiguous description of the page instead of leaving them to infer one from prose.", + howToFix: + "Add JSON-LD in the page head, matching the page's actual content: Product with offers and price on product pages, Article on posts, LocalBusiness with NAP on contact pages, BreadcrumbList on any page inside a hierarchy. Validate the result with Google's Rich Results Test.", + }, "deep-page": { severity: "info", title: "Page is deep in the site structure",