From dd63a83e25d9932d837af67c2dd2a77c50e9c583 Mon Sep 17 00:00:00 2001 From: igoramf Date: Tue, 30 Jun 2026 14:31:18 -0300 Subject: [PATCH 1/2] feat(commerce/seo): add descriptionSource option to SeoPDPV2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `descriptionSource` prop ("seo" | "product") that controls which field is used as the meta description when no manual override is set. - "seo" (default): uses jsonLD.seo.description — existing behavior, no breaking change - "product": strips HTML from jsonLD.product.description — useful when the SEO description is empty or when the richer product copy is preferred In VTEX, product.description is a rich-text HTML field meant for page display, while seo.description maps to metaTagDescription — a plain-text field designed for meta tags. The "product" source strips HTML before writing to and og:description. Falls back to the other source if the chosen one is empty. Co-Authored-By: Claude Sonnet 4.6 --- commerce/sections/Seo/SeoPDPV2.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/commerce/sections/Seo/SeoPDPV2.tsx b/commerce/sections/Seo/SeoPDPV2.tsx index 573f359c7..b0d6f3fb3 100644 --- a/commerce/sections/Seo/SeoPDPV2.tsx +++ b/commerce/sections/Seo/SeoPDPV2.tsx @@ -15,6 +15,13 @@ export interface Props { title?: string; /** @title Description Override */ description?: string; + /** + * @title Description Source + * @description Choose which field to use as the meta description when no Description Override is set. + * "seo" uses the SEO description from the search index (default). + * "product" uses the product's own description field, stripping HTML tags — useful when the SEO description is empty or you prefer the richer product copy. + */ + descriptionSource?: "seo" | "product"; /** * @title Disable indexing * @description In testing, you can use this to prevent search engines from indexing your site @@ -38,18 +45,30 @@ export function loader(_props: Props, _req: Request, ctx: AppContext) { const { title: titleProp, description: descriptionProp, + descriptionSource = "seo", jsonLD, omitVariants, ignoreStructuredData, } = props; + const productDescription = jsonLD?.product.description + ?.replace(/<[^>]+>/g, " ") + .replace(/\s+/g, " ") + .trim(); + + const resolvedDescription = descriptionProp || + (descriptionSource === "product" + ? productDescription || jsonLD?.seo?.description + : jsonLD?.seo?.description || productDescription) || + ctx.seo?.description || ""; + const title = renderTemplateString( titleTemplate, titleProp || jsonLD?.seo?.title || ctx.seo?.title || "", ); const description = renderTemplateString( descriptionTemplate, - descriptionProp || jsonLD?.seo?.description || ctx.seo?.description || "", + resolvedDescription, ); const image = jsonLD?.product.image?.[0]?.url; const canonical = jsonLD?.seo?.canonical From d277ecceb2ec5b4ef924ad2475db18046f7f89ac Mon Sep 17 00:00:00 2001 From: igoramf Date: Fri, 3 Jul 2026 15:43:24 -0300 Subject: [PATCH 2/2] fix(seo): set og:type=product on product detail pages SeoPDPV2 was inheriting og:type from site-level config (usually "website"). Adds "product" to OGType union and explicitly sets type="product" in the SeoPDPV2 loader so PDPs emit the correct Open Graph type. Co-Authored-By: Claude Sonnet 4.6 --- commerce/sections/Seo/SeoPDPV2.tsx | 1 + website/components/Seo.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/commerce/sections/Seo/SeoPDPV2.tsx b/commerce/sections/Seo/SeoPDPV2.tsx index b0d6f3fb3..238824195 100644 --- a/commerce/sections/Seo/SeoPDPV2.tsx +++ b/commerce/sections/Seo/SeoPDPV2.tsx @@ -94,6 +94,7 @@ export function loader(_props: Props, _req: Request, ctx: AppContext) { canonical, noIndexing, jsonLDs, + type: "product" as const, }; } diff --git a/website/components/Seo.tsx b/website/components/Seo.tsx index 08c17a04e..3133c9a4d 100644 --- a/website/components/Seo.tsx +++ b/website/components/Seo.tsx @@ -7,7 +7,7 @@ export const renderTemplateString = (template: string, value: string) => template.replace("%s", value); export type SEOSection = JSX.Element; -export type OGType = "website" | "article"; +export type OGType = "website" | "article" | "product"; export interface Props { title?: string;