218 lines
7.1 KiB
JavaScript
218 lines
7.1 KiB
JavaScript
/** Safe for double-quoted HTML attributes */
|
|
const escAttr = (str) =>
|
|
String(str ?? "")
|
|
.replace(/&/g, "&")
|
|
.replace(/"/g, """)
|
|
.replace(/</g, "<");
|
|
|
|
/**
|
|
* Head tags for prerendered category URLs — explicit canonical per /Kategorie/{slug}
|
|
* so Google does not cluster different listing pages (e.g. neu vs Seeds) as duplicates.
|
|
*/
|
|
const generateCategoryMetaTags = (category, baseUrl, config) => {
|
|
const root = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
const categoryUrl = `${root}/Kategorie/${category.seoName}`;
|
|
const name = category.name || `Kategorie ${category.seoName}`;
|
|
const site = config.siteName || config.brandName;
|
|
const desc = `${name} bei ${config.brandName}: Growshop-Sortiment online kaufen. Schnelle Lieferung, Laden Dresden.`;
|
|
const descShort = desc.length > 160 ? `${desc.slice(0, 157)}...` : desc;
|
|
const e = escAttr;
|
|
const logoUrl =
|
|
config.images && config.images.logo
|
|
? `${root}${config.images.logo}`
|
|
: `${root}/assets/images/nopicture.jpg`;
|
|
|
|
return `
|
|
<meta name="description" content="${e(descShort)}">
|
|
<meta property="og:title" content="${e(`${name} | ${site}`)}">
|
|
<meta property="og:description" content="${e(descShort)}">
|
|
<meta property="og:url" content="${categoryUrl}">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:image" content="${e(logoUrl)}">
|
|
<meta property="og:site_name" content="${e(site)}">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="${e(`${name} | ${site}`)}">
|
|
<meta name="twitter:description" content="${e(descShort)}">
|
|
<meta name="robots" content="index, follow">
|
|
<link rel="canonical" href="${categoryUrl}">
|
|
`;
|
|
};
|
|
|
|
const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
|
// Category IDs to skip (seeds, plants, headshop items)
|
|
const skipCategoryIds = [689, 706, 709, 711, 714, 748, 749, 896, 710, 924, 923, 922, 921, 916, 278, 259, 258];
|
|
|
|
// Check if category ID is in skip list
|
|
if (category.id && skipCategoryIds.includes(parseInt(category.id))) {
|
|
return '';
|
|
}
|
|
|
|
const root = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
const categoryUrl = `${root}/Kategorie/${category.seoName}`;
|
|
|
|
// Calculate price valid date (current date + 3 months)
|
|
const priceValidDate = new Date();
|
|
priceValidDate.setMonth(priceValidDate.getMonth() + 3);
|
|
const priceValidUntil = priceValidDate.toISOString().split("T")[0];
|
|
|
|
const id = {
|
|
business: `${root}#business`,
|
|
website: `${root}#website`,
|
|
breadcrumb: `${categoryUrl}#breadcrumb`,
|
|
itemList: `${categoryUrl}#itemlist`,
|
|
};
|
|
|
|
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 breadcrumbNode = {
|
|
"@id": id.breadcrumb,
|
|
"@type": "BreadcrumbList",
|
|
itemListElement: [
|
|
{
|
|
"@type": "ListItem",
|
|
position: 1,
|
|
name: "Home",
|
|
item: root,
|
|
},
|
|
{
|
|
"@type": "ListItem",
|
|
position: 2,
|
|
name: category.name,
|
|
item: categoryUrl,
|
|
},
|
|
],
|
|
};
|
|
|
|
const collectionPageNode = {
|
|
"@id": categoryUrl,
|
|
"@type": "CollectionPage",
|
|
name: category.name,
|
|
url: categoryUrl,
|
|
description: `${category.name} - Entdecken Sie unsere Auswahl an hochwertigen Produkten`,
|
|
isPartOf: { "@id": id.website },
|
|
breadcrumb: { "@id": id.breadcrumb },
|
|
};
|
|
|
|
const graph = [businessNode, websiteNode, breadcrumbNode, collectionPageNode];
|
|
|
|
// Add product list if products are available
|
|
if (products && products.length > 0) {
|
|
collectionPageNode.mainEntity = { "@id": id.itemList };
|
|
|
|
graph.push({
|
|
"@id": id.itemList,
|
|
"@type": "ItemList",
|
|
numberOfItems: products.length,
|
|
itemListElement: products.slice(0, 20).map((product, index) => ({
|
|
"@type": "ListItem",
|
|
position: index + 1,
|
|
item: {
|
|
"@type": "Product",
|
|
name: product.name,
|
|
url: `${root}/Artikel/${product.seoName}`,
|
|
image:
|
|
product.pictureList && product.pictureList.trim()
|
|
? `${root}/assets/images/prod${product.pictureList
|
|
.split(",")[0]
|
|
.trim()}.avif`
|
|
: `${root}/assets/images/nopicture.jpg`,
|
|
description: product.description
|
|
? product.description.replace(/<[^>]*>/g, "").substring(0, 200)
|
|
: `${product.name} - Hochwertiges Growshop Produkt`,
|
|
sku: product.articleNumber || product.seoName,
|
|
brand: {
|
|
"@type": "Brand",
|
|
name: product.manufacturer || config.brandName,
|
|
},
|
|
offers: {
|
|
"@type": "Offer",
|
|
url: `${root}/Artikel/${product.seoName}`,
|
|
price:
|
|
product.price && !isNaN(product.price)
|
|
? product.price.toString()
|
|
: "0.00",
|
|
priceCurrency: config.currency,
|
|
priceValidUntil: priceValidUntil,
|
|
availability: product.available
|
|
? "https://schema.org/InStock"
|
|
: "https://schema.org/OutOfStock",
|
|
seller: { "@id": id.business },
|
|
itemCondition: "https://schema.org/NewCondition",
|
|
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 categoryGraph = {
|
|
"@context": "https://schema.org",
|
|
"@graph": graph,
|
|
};
|
|
|
|
return `<script type="application/ld+json">${JSON.stringify(
|
|
categoryGraph
|
|
)}</script>`;
|
|
};
|
|
|
|
module.exports = {
|
|
generateCategoryMetaTags,
|
|
generateCategoryJsonLd,
|
|
}; |