Product JSON-LD: add sku + gallery images, single-source the meta description
sku was tracked in Payload but never exposed to the frontend at all — added to the Product type and wired into buildProductSchema (a real Schema.org property Google's rich-result validator checks for). image is now an array (main + gallery) when a product has gallery photos, instead of always just the one main image. /der-eine's page metadata description was a separately hardcoded string that had drifted from Products.description (the one JSON-LD/ cart/checkout actually use) — now reads from the live product via generateMetadata, same pattern /tasse-die-pause already uses. Updated Payload's own description field to the more product-descriptive text that used to live only in that hardcoded string.
This commit is contained in:
+15
-16
@@ -9,25 +9,24 @@ import { getProductBySlug, getCompanySettings } from "../lib/payload";
|
||||
import { buildProductSchema } from "../lib/structuredData";
|
||||
|
||||
const title = "Der Eine. – Für die eine Sache, die gerade zählt.";
|
||||
const description =
|
||||
"Der Eine. ist ein schlichter Kugelschreiber aus Metall. Angenehm in der Hand, klein genug für die Tasche und gemacht für alles, was kurz raus aus dem Kopf und irgendwo hin muss.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: "/der-eine",
|
||||
},
|
||||
openGraph: {
|
||||
// Description reads from the live product instead of a separately
|
||||
// hardcoded string, unlike this page's own `title` — matches
|
||||
// /tasse-die-pause's own pattern (see that page's comment). Two
|
||||
// hand-maintained copies of the same text used to drift apart: this one
|
||||
// used to say something different from Products.description (which feeds
|
||||
// the cart/checkout/JSON-LD elsewhere) — same field, one source now.
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const product = await getProductBySlug("stift-kugelschreiber");
|
||||
const description = product?.description ?? undefined;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
url: "/der-eine",
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
alternates: { canonical: "/der-eine" },
|
||||
openGraph: { title, description, url: "/der-eine" },
|
||||
twitter: { title, description },
|
||||
};
|
||||
}
|
||||
|
||||
export default async function DerEinePage() {
|
||||
const [product, seller] = await Promise.all([
|
||||
|
||||
@@ -7,6 +7,7 @@ const product = (overrides: Partial<Product> = {}): Product => ({
|
||||
numericId: 1,
|
||||
name: "ToDo-Karten",
|
||||
description: "",
|
||||
sku: null,
|
||||
price: 12.9,
|
||||
compareAtPrice: null,
|
||||
image: "",
|
||||
|
||||
@@ -182,6 +182,10 @@ export type Product = {
|
||||
numericId: number;
|
||||
name: string;
|
||||
description: string;
|
||||
// Not shown anywhere in the UI today — only consumed by
|
||||
// buildProductSchema (JSON-LD's Product.sku), a real Schema.org property
|
||||
// Google's rich-result validator looks for.
|
||||
sku: string | null;
|
||||
price: number;
|
||||
compareAtPrice: number | null;
|
||||
image: string;
|
||||
@@ -264,6 +268,7 @@ type PayloadProduct = {
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
sku: string | null;
|
||||
price: number;
|
||||
compareAtPrice: number | null;
|
||||
image: { url: string } | number | null;
|
||||
@@ -330,6 +335,7 @@ export function mapPayloadProduct(product: PayloadProduct): Product {
|
||||
numericId: product.id,
|
||||
name: product.name,
|
||||
description: product.description ?? "",
|
||||
sku: product.sku || null,
|
||||
price: product.price,
|
||||
compareAtPrice: product.compareAtPrice ?? null,
|
||||
image: typeof product.image === "object" && product.image ? product.image.url : "",
|
||||
|
||||
@@ -54,8 +54,9 @@ export function buildProductSchema(product: Product, url: string, seller: Compan
|
||||
"@type": "Product",
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
image: product.image,
|
||||
image: product.gallery.length > 0 ? [product.image, ...product.gallery] : product.image,
|
||||
url,
|
||||
...(product.sku ? { sku: product.sku } : {}),
|
||||
// No reviews/ratings system exists yet — `aggregateRating` is
|
||||
// optional in the spec and deliberately omitted rather than faked;
|
||||
// add it here once real reviews exist, not before.
|
||||
|
||||
Reference in New Issue
Block a user