Implementing JSON-LD Structured Data (Products & Breadcrumbs)

How to declare the data that puts price, stock, and hierarchy into search results on a headless EC site

JSON-LDStructured DataRich ResultsBreadcrumbsProduct Schema
8 min read

Introduction

Scan a page of search results and you'll see some listings that come with a price, stock status, even a hierarchy like "Home > Category > Product." Meanwhile your own site shows a blue title and two lines of description. Same ranking position, very different pull.

That extra information isn't something search engines infer from the body text. It appears only because the page declares, in a machine-readable form, "what's written here is a price." With an off-the-shelf EC theme that declaration is produced automatically, and moving to headless stops it entirely.

This article covers what I declare on product pages, category pages, and the FAQ of an EC site selling motorcycle gear, plus the rules I settled on after running it for a while.

Structured Data Is a Second Copy of the Page, Invisible on Screen

The Same Content Held Two Ways: for People and for Machines

Structured data is a small block of information embedded in the HTML that never appears on screen. On a product page, the photo, name, and price are the part people see; the type, price, currency, stock, and hierarchy are the part machines read. Hold the same content in two formats — that's the whole idea.

The Two Faces of a Single Product Page
What people see

Photo, product name, price, description

What machines read

Type, price, currency, stock, hierarchy

The same content, in two formats
Search engine

Results carrying price, stock, and hierarchy

The point of the diagram: one page has two faces, one for people and one for machines.

The condition is that the two never contradict each other. Information declared only for machines that doesn't exist on screen, or a declared price that differs from the displayed one, gets the page treated as untrustworthy. You can't quietly inflate just one side.

Why I Chose JSON-LD

There are several ways to write structured data. One approach attaches attributes to individual HTML tags; I went with JSON-LD instead. It sits apart from the body HTML as a single block of declaration data on the page, so the visible markup stays untouched.

Ways to Write Structured Data
BEFORE
Embedded in tags

Scattered across HTML attributes. Breaks easily when the design changes

AFTER
JSON-LD

Written as one block separate from the body. Managed in a single place

In other words, you layer one sheet of declaration data on top of the body you built with SSR (server-side rendering, where the server assembles the HTML before returning it). It also means a design change is far less likely to break the declarations.

Three Kinds of Data on Product Pages

The Product Itself

This is the one with the biggest effect. The declared fields are the product name, description, brand, image, price, currency, stock status, product page URL, and product code.

With those fields in place, price and stock can start appearing in search results.

One thing I watch is what happens when a shopper switches color or size. In a headless build, variant switching (the same product in a different color or size) finishes instantly on screen. If the displayed price changes while the declared value stays stale, screen and declaration diverge. So the values used for display are passed straight through to the declaration.

The reason a hierarchy like "Home > Riding Jackets > Product Name" shows in place of the URL is the breadcrumb declaration. It conveys the site structure and reads more clearly, which shows up in click-through rate.

Generating the Breadcrumb Declaration
Identify the hierarchy

The server determines which category the current page sits under

Order the items

Arrange them as Home → Category → Product

Attach names and URLs

Give each item its display name and page URL

Embed in the HTML

Output on the server from the same source as the visible breadcrumb

Follow those four steps and the visible breadcrumb and the declared hierarchy are built from identical material.

The fiddly part was deciding where the names in the hierarchy come from. Categories often carry two names: a title written for search results and a short label used inside the site. The former tends to be long and stuffed with search terms, and using it directly makes the hierarchy hard to read. So the display name is decided in one place, and the visible breadcrumb, the declaration, and the link back to the category from a product page all use that same value.

Organization Information

The third is a block covering the operator's name, logo, official site URL, and social accounts. It matters less for any single product page than for how the site appears when someone searches the brand name, and for overall trust. Unglamorous, but you build it once and reuse it everywhere.

Declarations for Category Pages and the FAQ

Conveying Exactly the 24 Items on Display

Category listing pages get a declaration saying "this page collects products," along with a list of the products shown. The list carries display order, product name, product page URL, and a description excerpt.

The count is set to the 24 items shown when the page first opens. Adding products that aren't on screen would put the display and the declaration out of sync. It uses exactly the same material as the SSR output for the category page.

The Question-and-Answer Page

Pages of questions and answers declare those pairs directly. Every question on the page is collected, and the answers go in as plain strings with formatting tags removed.

Worth noting: the search result format that expands questions and answers inline is currently limited to certain kinds of sites, such as government and medical organizations. Declaring it on an EC site won't change how the results look right now. I still output it because conveying that the page consists of questions and answers has value on its own — and display formats change over time, so holding the data in the correct shape is a reasonable investment.

Three Rules I Settled On

Keep a Single Source for Every Value

Calculate the display price and the declared price separately and they will diverge the moment a discount or member price enters the picture. Pass the displayed value straight through and that class of mismatch simply doesn't occur. It's also an easy rule to remember, so whoever touches the code next isn't left guessing.

Don't Declare Too Many Types

Adding data that doesn't correspond to what's on the page has no effect; it just produces more validation errors and more to maintain. For product pages, three types — product, breadcrumbs, and organization — were plenty. Keeping the list short also means fewer places to check when you review it later.

Always Validate After Publishing

Structured data renders perfectly on screen even if one character is wrong, so mistakes go unnoticed. I run product and category page URLs through the testing tool search engines publish, one at a time, to confirm the intended fields are being read. The parts built with help from an AI agent (AI-assisted development) are exactly the ones where I keep that manual verification step.

Conclusion

Structured data is the work of restating a page's content in a machine-readable format and attaching it. Product pages get the product, breadcrumbs, and organization; category pages get the list of products on display; question pages get the questions and answers. Hold to one rule — everything must match what's shown on screen — and none of it is difficult.

The work itself is unglamorous, but how your listings look in search changes visibly even at the same ranking. It's worth building early, alongside your SSR output.