Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/server/lib/audit/issues/page-reporters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function makePage(overrides: Partial<CrawledPageResult>): CrawledPageResult {
imagesMissingAlt: 0,
images: [],
links: [HEALTHY_LINK],
hasStructuredData: false,
hasStructuredData: true,
hreflangTags: [],
isIndexable: true,
responseTimeMs: 200,
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/server/lib/audit/issues/page-reporters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
8 changes: 8 additions & 0 deletions src/shared/audit-issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down