diff --git a/package.json b/package.json index 5cdd05d15..1b796b03a 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "postcss": "8.4.31", "prettier": "3.0.3", "prettier-plugin-tailwindcss": "0.5.7", + "schema-dts": "1.1.2", "tailwindcss": "3.3.5", "typescript": "5.2.2", "wonka": "6.3.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0b24ca7b..1f3940042 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,6 +169,9 @@ devDependencies: prettier-plugin-tailwindcss: specifier: 0.5.7 version: 0.5.7(prettier@3.0.3) + schema-dts: + specifier: 1.1.2 + version: 1.1.2(typescript@5.2.2) tailwindcss: specifier: 3.3.5 version: 3.3.5 @@ -5476,6 +5479,14 @@ packages: loose-envify: 1.4.0 dev: false + /schema-dts@1.1.2(typescript@5.2.2): + resolution: {integrity: sha512-MpNwH0dZJHinVxk9bT8XUdjKTxMYrA5bLtrrGmFA6PTLwlOKnhi67XoRd6/ty+Djt6ZC0slR57qFhZDNMI6DhQ==} + peerDependencies: + typescript: '>=4.1.0' + dependencies: + typescript: 5.2.2 + dev: true + /scuid@1.1.0: resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==} dev: true diff --git a/src/app/(main)/products/[slug]/page.tsx b/src/app/(main)/products/[slug]/page.tsx index d4b124fe8..ea340558c 100644 --- a/src/app/(main)/products/[slug]/page.tsx +++ b/src/app/(main)/products/[slug]/page.tsx @@ -5,6 +5,7 @@ import { notFound } from "next/navigation"; import { type ResolvingMetadata, type Metadata } from "next"; import xss from "xss"; import invariant from "ts-invariant"; +import { type WithContext, type Product } from "schema-dts"; import { AddButton } from "./AddButton"; import { VariantSelector } from "@/ui/components/VariantSelector"; import { ProductImageWrapper } from "@/ui/atoms/ProductImageWrapper"; @@ -39,7 +40,6 @@ export async function generateMetadata( const productName = product.seoTitle || product.name; const variantName = product.variants?.find(({ id }) => id === searchParams.variant)?.name; - const productNameAndVariant = variantName ? `${productName} - ${variantName}` : productName; return { @@ -50,6 +50,16 @@ export async function generateMetadata( ? process.env.NEXT_PUBLIC_STOREFRONT_URL + `/products/${encodeURIComponent(params.slug)}` : undefined, }, + openGraph: product.thumbnail + ? { + images: [ + { + url: product.thumbnail.url, + alt: product.name, + }, + ], + } + : null, }; } @@ -125,8 +135,47 @@ export default async function Page(props: { params: { slug: string }; searchPara }) : ""; + const productJsonLd: WithContext = { + "@context": "https://schema.org", + "@type": "Product", + image: product.thumbnail?.url, + ...(selectedVariant + ? { + name: `${product.name} - ${selectedVariant.name}`, + description: product.seoDescription || `${product.name} - ${selectedVariant.name}`, + offers: { + "@type": "Offer", + availability: selectedVariant.quantityAvailable + ? "https://schema.org/InStock" + : "https://schema.org/OutOfStock", + priceCurrency: selectedVariant.pricing?.price?.gross.currency, + price: selectedVariant.pricing?.price?.gross.amount, + }, + } + : { + name: product.name, + + description: product.seoDescription || product.name, + offers: { + "@type": "AggregateOffer", + availability: product.variants?.some((variant) => variant.quantityAvailable) + ? "https://schema.org/InStock" + : "https://schema.org/OutOfStock", + priceCurrency: product.pricing?.priceRange?.start?.gross.currency, + lowPrice: product.pricing?.priceRange?.start?.gross.amount, + highPrice: product.pricing?.priceRange?.stop?.gross.amount, + }, + }), + }; + return (
+