refactor: Enhance JSON-LD structure in category and product generation functions for improved SEO and consistency across URLs
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user