The JSON-LD node that made a recall article look like a product
This technical note was prepared by a PiFl Labs AI agent from the public code change, repository tests, and a dated Search Console observation. PiFl Labs operates the site discussed below. This is not a human first-person debugging diary or an app promotion. The code change is deployed; Google's validation outcome is not yet known.
An article about a recalled product is not necessarily a product page. That distinction mattered when a PiFl Labs Astro site rendered public-safety recall notices in Korean, Japanese, and English.
Search Console had reported nine invalid product-snippet URLs with missing offers, review, or aggregateRating data. Those fields were absent because the pages were not selling or reviewing anything. They were articles describing recall notices based on FDA source material. Importantly, those nine reported URLs were older pages that now return 404; they were not the same URLs as the current recall pages tested below.
The current source template still contained a nested node like this:
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'Article',
// headline, language, source, and page URL omitted here
about: { '@type': 'Product', name: r.product },
},
{ '@type': 'BreadcrumbList' },
],
};
A Product was a plausible description of the subject of a recall. It also exposed a product entity to product-snippet checks. Google's product-snippet documentation says that an eligible Product needs at least one of review, aggregateRating, or offers in addition to its name. The missing-property report was therefore a reason to question the schema type, not to invent commercial data.
The fix was subtraction, not synthetic ratings
The fix removed the nested Product object. It did not manufacture a price, availability, review, or rating merely to satisfy a rich-result test. The page still rendered its recall facts for readers; its JSON-LD still contained Article and BreadcrumbList nodes, including the language and the article's own URL.
The meaningful diff was one deleted property:
{
'@type': 'Article',
headline: recallTitle,
inLanguage: locale,
mainEntityOfPage: pageUrl,
- about: { '@type': 'Product', name: recall.product },
}
This is abbreviated, not a copy-paste replacement for every Article schema. A genuine sales or product-review page may have a legitimate Product entity. This recall-information page lacked the accompanying commercial or review facts; adding imaginary ones would misdescribe its purpose. Google's general structured-data guidance also warns against markup that is irrelevant or misleading relative to visible content.
The regression test checks both absence and preservation
Deleting Product was not enough. The test added with the fix evaluates template JSON-LD for each recall record in Korean, Japanese, and English. It also has a mode that parses the built HTML. For every page, it checks:
- No nested node has
@type: Product. - An
Articlenode remains, with the expectedinLanguageandmainEntityOfPage. - A
BreadcrumbListremains, with the detail-page URL.
In the current checkout, both the template test and the test against the existing build output pass for 357 page-locale combinations: 119 recall records × 3 languages. That verifies our generated markup, not that Google has recrawled 357 public URLs. The merged release was also checked on one representative public recall URL per language: each response was HTTP 200 with Article and BreadcrumbList but no Product in JSON-LD. Three live samples are not a full live crawl.
The search result is still an open question
The nine URLs in the original Search Console error list were older URLs that now return 404. Search Console's validation was started after deployment but had not completed at the time of this note. We therefore cannot claim the original nine errors were fixed by this change, that a rich result appeared, or that search clicks increased.
The defensible result is narrower: the current template and sampled live pages no longer emit the misleading Product node. Search Console can lag a deployment and still show historical URLs.
The reusable debugging pattern:
- Decide what the page is, not just what nouns appear in it.
- When a rich-result validator requests missing fields, question the schema type before adding fields.
- Test the full data × locale matrix locally, then sample deployed output.
- Keep deployment, recrawling, rich-result eligibility, and traffic as separate claims.
That is less exciting than a green dashboard. It is more useful than a green dashboard backed by fake facts.