refactor: Enhance JSON-LD structure in category and product generation functions for improved SEO and consistency across URLs

This commit is contained in:
sebseb7
2026-03-28 17:10:14 +01:00
parent c503de3a11
commit 21d86565f1
3 changed files with 396 additions and 278 deletions

View File

@@ -68,14 +68,15 @@ const generateProductMetaTags = (product, baseUrl, config) => {
};
const generateProductJsonLd = (product, baseUrl, config, categoryInfo = null) => {
const productUrl = `${baseUrl}/Artikel/${product.seoName}`;
const root = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
const productUrl = `${root}/Artikel/${product.seoName}`;
const pictureFirstId =
product.pictureList && product.pictureList.trim()
? product.pictureList.split(",")[0].trim()
: null;
const imageUrl = pictureFirstId
? `${baseUrl}/assets/images/prod${pictureFirstId}.avif`
: `${baseUrl}/assets/images/nopicture.jpg`;
? `${root}/assets/images/prod${pictureFirstId}.avif`
: `${root}/assets/images/nopicture.jpg`;
// Clean description for JSON-LD (remove HTML tags)
const cleanDescription = product.description
@@ -86,8 +87,87 @@ const generateProductJsonLd = (product, baseUrl, config, categoryInfo = null) =>
const priceValidDate = new Date();
priceValidDate.setMonth(priceValidDate.getMonth() + 3);
const productJsonLd = {
"@context": "https://schema.org/",
const id = {
business: `${root}#business`,
website: `${root}#website`,
product: `${productUrl}#product`,
breadcrumb: `${productUrl}#breadcrumb`,
};
const logoUrl =
config.images && config.images.logo
? `${root}${config.images.logo}`
: undefined;
const businessNode = {
"@id": id.business,
"@type": ["GardenStore", "LocalBusiness", "Organization"],
name: config.brandName,
url: root,
...(logoUrl && {
logo: { "@type": "ImageObject", url: logoUrl },
image: { "@type": "ImageObject", url: logoUrl },
}),
};
const websiteNode = {
"@id": id.website,
"@type": "WebSite",
name: config.siteName || config.brandName,
url: root,
publisher: { "@id": id.business },
};
const offer = {
"@type": "Offer",
url: productUrl,
priceCurrency: config.currency,
price: product.price.toString(),
priceValidUntil: priceValidDate.toISOString().split("T")[0],
itemCondition: "https://schema.org/NewCondition",
availability: product.available
? "https://schema.org/InStock"
: "https://schema.org/OutOfStock",
seller: { "@id": id.business },
hasMerchantReturnPolicy: {
"@type": "MerchantReturnPolicy",
applicableCountry: "DE",
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
merchantReturnDays: 14,
returnMethod: "https://schema.org/ReturnByMail",
returnFees: "https://schema.org/FreeReturn",
},
shippingDetails: {
"@type": "OfferShippingDetails",
shippingRate: {
"@type": "MonetaryAmount",
value: 5.9,
currency: "EUR",
},
shippingDestination: {
"@type": "DefinedRegion",
addressCountry: "DE",
},
deliveryTime: {
"@type": "ShippingDeliveryTime",
handlingTime: {
"@type": "QuantitativeValue",
minValue: 0,
maxValue: 1,
unitCode: "DAY",
},
transitTime: {
"@type": "QuantitativeValue",
minValue: 2,
maxValue: 3,
unitCode: "DAY",
},
},
},
};
const productNode = {
"@id": id.product,
"@type": "Product",
name: product.name,
image: [imageUrl],
@@ -98,95 +178,65 @@ const generateProductJsonLd = (product, baseUrl, config, categoryInfo = null) =>
"@type": "Brand",
name: product.manufacturer || "Unknown",
},
offers: {
"@type": "Offer",
url: productUrl,
priceCurrency: config.currency,
price: product.price.toString(),
priceValidUntil: priceValidDate.toISOString().split("T")[0],
itemCondition: "https://schema.org/NewCondition",
availability: product.available
? "https://schema.org/InStock"
: "https://schema.org/OutOfStock",
seller: {
"@type": "Organization",
name: config.brandName,
},
hasMerchantReturnPolicy: {
"@type": "MerchantReturnPolicy",
applicableCountry: "DE",
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
merchantReturnDays: 14,
returnMethod: "https://schema.org/ReturnByMail",
returnFees: "https://schema.org/FreeReturn",
},
shippingDetails: {
"@type": "OfferShippingDetails",
shippingRate: {
"@type": "MonetaryAmount",
value: 5.90,
currency: "EUR",
},
shippingDestination: {
"@type": "DefinedRegion",
addressCountry: "DE",
},
deliveryTime: {
"@type": "ShippingDeliveryTime",
handlingTime: {
"@type": "QuantitativeValue",
minValue: 0,
maxValue: 1,
unitCode: "DAY",
},
transitTime: {
"@type": "QuantitativeValue",
minValue: 2,
maxValue: 3,
unitCode: "DAY",
},
},
},
},
offers: offer,
};
const productScript = `<script type="application/ld+json">${JSON.stringify(
productJsonLd
const hasBreadcrumb =
categoryInfo && categoryInfo.name && categoryInfo.seoName;
const breadcrumbList = hasBreadcrumb
? {
"@id": id.breadcrumb,
"@type": "BreadcrumbList",
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: "Home",
item: root,
},
{
"@type": "ListItem",
position: 2,
name: categoryInfo.name,
item: `${root}/Kategorie/${categoryInfo.seoName}`,
},
{
"@type": "ListItem",
position: 3,
name: product.name,
item: productUrl,
},
],
}
: null;
const itemPageNode = {
"@id": productUrl,
"@type": "ItemPage",
url: productUrl,
name: product.name,
isPartOf: { "@id": id.website },
mainEntity: { "@id": id.product },
...(hasBreadcrumb && { breadcrumb: { "@id": id.breadcrumb } }),
};
const graph = [
businessNode,
websiteNode,
itemPageNode,
...(breadcrumbList ? [breadcrumbList] : []),
productNode,
];
const productGraph = {
"@context": "https://schema.org",
"@graph": graph,
};
return `<script type="application/ld+json">${JSON.stringify(
productGraph
)}</script>`;
// BreadcrumbList is not a valid property on Product; emit as its own JSON-LD block (WebPage path context).
if (categoryInfo && categoryInfo.name && categoryInfo.seoName) {
const breadcrumbJsonLd = {
"@context": "https://schema.org/",
"@type": "BreadcrumbList",
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: "Home",
item: baseUrl,
},
{
"@type": "ListItem",
position: 2,
name: categoryInfo.name,
item: `${baseUrl}/Kategorie/${categoryInfo.seoName}`,
},
{
"@type": "ListItem",
position: 3,
name: product.name,
item: productUrl,
},
],
};
const breadcrumbScript = `<script type="application/ld+json">${JSON.stringify(
breadcrumbJsonLd
)}</script>`;
return `${productScript}\n${breadcrumbScript}`;
}
return productScript;
};
module.exports = {