Skip to content

Render product structured data from the theme - #88

Merged
next-devin merged 4 commits into
mainfrom
feat/product-structured-data
Nov 3, 2026
Merged

next-devin merged 4 commits into
mainfrom
feat/product-structured-data

Conversation

@next-devin

Copy link
Copy Markdown
Contributor

Product pages now render their own meta tags, Open Graph tags and schema.org JSON-LD from partials/product_structured_data.html, instead of the platform {% seo %} output.

  • A product with variants is a ProductGroup with one Product and Offer per variant: price, currency, availability, SKU, GTIN, image and description.
  • Brand, GTIN and MPN come from product metadata keys brand, gtin and mpn when the store defines them. Without brand the store name is used. The UPC is used as the GTIN only when it is 8, 12, 13 or 14 digits, and is no longer emitted as mpn.
  • Every string goes through escapejs, so line breaks, quotes and backslashes in product text produce valid JSON. The platform markup is invalid JSON for any multi-line description.
  • The offer price is the price shown on the page; the rating is included only when Product Reviews is on.

Documented in docs/pdp-customization.md, "Structured data".

Verification

  • make dtl, make test, make contract and make css-check pass; assets/main.css is unchanged.
  • Rendered under Django 4.2 with a title containing " and </script>, a description with a line break and backslash, products with and without variants, images, descriptions and a valid UPC: json.loads succeeds in every case and </script> stays inside the JSON string.
  • On a NEXT dev store, a product with 4 variants on a preview theme: Google's Rich Results Test reports 5 valid items (Product snippets 1, Merchant listings 4) with no critical issues. The same page on the platform markup fails to parse. Remaining non-critical notes are optional shippingDetails and hasMerchantReturnPolicy.

Closes #87

Product pages override the seo block with partials/product_structured_data.html:
ProductGroup with per-variant Product and Offer, brand/gtin/mpn from product
metadata, UPC as GTIN only when it has a valid length, the displayed price,
and every JSON-LD string escaped with escapejs so a quote in a title no longer
breaks the markup.
Google's merchant listings test rejects a ProductGroup variant without an image.
Comment thread partials/product_structured_data.html Resolved
Comment thread partials/product_structured_data.html Resolved
Comment thread partials/product_structured_data.html Resolved
Comment thread partials/product_structured_data.html Resolved
Comment thread partials/product_structured_data.html Resolved
Comment thread partials/product_structured_data.html Resolved
Comment thread partials/product_structured_data.html Resolved
Comment thread partials/product_structured_data.html Resolved
Comment thread templates/catalogue/product.html Resolved
Comment thread docs/pdp-customization.md Resolved
@kilo-code-bot

kilo-code-bot Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
partials/product_structured_data.html 6 upscale=True remains an unverified template boolean expression; if the platform image tag does not expose True as a context builtin, the meta thumbnail is not upscaled and og:image can be smaller than 1200x630
partials/product_structured_data.html 49 product.children.all still publishes every child's SKU, price, currency and availability, including any non-public child; the public JSON-LD should use the same publishable-child queryset as the storefront

SUGGESTION

File Line Issue
partials/product_structured_data.html 12 The product-specific robots tag is emitted in addition to the unconditional tag in layouts/base.html:10 and the possible site_index tag, leaving conflicting crawler directives
Files Reviewed (3 files)
  • partials/product_structured_data.html - 3 issues
  • docs/pdp-customization.md - 0 issues
  • CHANGELOG.md - 0 issues

Fix these issues in Kilo Cloud

Previous Review Summary (commit 85109bd)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 85109bd)

Status: 15 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 10
SUGGESTION 5
Issue Details (click to expand)

WARNING

File Line Issue
partials/product_structured_data.html 6 upscale=True is a template variable, not a boolean literal, so the thumbnailer never upscales and og:image can be far smaller than 1200x630
partials/product_structured_data.html 15 og:type value og:product is not a valid Open Graph type; consumers fall back to website and render no product card
partials/product_structured_data.html 17 product.get_primary_url is used nowhere else in Spark (the theme uses product.get_absolute_url); og:url, url and both offers.url values can render empty, and Google requires offers.url
partials/product_structured_data.html 18 product.get_meta_image is unverified and unguarded; for a product with no image (FX-P02) og:image ships empty and the JSON-LD image fallback becomes ""
partials/product_structured_data.html 19 Brand fallback uses shop_name, a context variable this theme never uses; the store name here is store.name, so an undefined shop_name yields an empty brand
partials/product_structured_data.html 35 "image": [""] when the product has no image, and the block now renders a new thumbnail per product image and per variant (FX-P04: 12 images, FX-P05: 64 variants)
partials/product_structured_data.html 38 product.metadata.<key> can silently resolve to nothing if the platform exposes metadata as the JSON blob its GraphQL clients parse; brand, GTIN and MPN would then never be emitted, with no error
partials/product_structured_data.html 50 product.children.all publishes unpublished children's SKU, price and availability in the public JSON-LD, and hasVariant can render as []
partials/product_structured_data.html 63 An Offer is emitted without price/priceCurrency when the product has no price record (FX-P13); both are required for a Google merchant listing
templates/catalogue/product.html 6 Replacing {% seo %} drops whatever the platform tag rendered (canonical link included) and the new partial emits no canonical URL, which contradicts the CHANGELOG claim that the meta and Open Graph tags are unchanged

SUGGESTION

File Line Issue
partials/product_structured_data.html 12 A second <meta name="robots"> on top of the unconditional one in layouts/base.html:10 and the possible site_index one
partials/product_structured_data.html 20 product:availability uses in stock/out of stock; the Open Graph product extension defines instock/outofstock/preorder
partials/product_structured_data.html 30 product.is_parent is not an accessor this theme uses anywhere; if it is missing the page silently degrades to a plain Product with no variants
partials/product_structured_data.html 59 The UPC is the only JSON-LD string written without escapejs, so Django's HTML autoescaping applies inside the <script> block
docs/pdp-customization.md 170 The GTIN row is looser than the code (the UPC must also be all digits; a parent's own gtin is never emitted) and the "every string goes through escapejs" rule does not hold for the UPC
Files Reviewed (4 files)
  • partials/product_structured_data.html - 13 issues
  • templates/catalogue/product.html - 1 issue
  • docs/pdp-customization.md - 1 issue
  • CHANGELOG.md - 0 issues

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 93.4K · Output: 4.2K · Cached: 557.8K

Omit offers when a product or variant has no price record and omit image
when neither the product nor the store has one. Cap the image list at three,
escape the UPC like every other string, and correct og:type and
product:availability to their defined values.
@next-devin
next-devin merged commit 2f1541f into main Nov 3, 2026
2 checks passed
@next-devin
next-devin deleted the feat/product-structured-data branch November 3, 2026 06:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Render product structured data (JSON-LD) from the theme

1 participant