feat: Add generateCategoryMetaTags function for enhanced SEO in category pages and integrate it into prerender process

This commit is contained in:
sebseb7
2026-03-28 17:21:43 +01:00
parent 21d86565f1
commit 36360df648
3 changed files with 52 additions and 2 deletions

View File

@@ -125,6 +125,7 @@ const {
const {
generateProductMetaTags,
generateProductJsonLd,
generateCategoryMetaTags,
generateCategoryJsonLd,
generateHomepageMetaTags,
generateHomepageJsonLd,
@@ -621,19 +622,25 @@ const renderApp = async (categoryData, socket) => {
const filename = `Kategorie/${category.seoName}`;
const location = `/Kategorie/${category.seoName}`;
const description = `Category "${category.name}" (ID: ${category.id})`;
const categoryMetaTags = generateCategoryMetaTags(
category,
shopConfig.baseUrl,
shopConfig
);
const categoryJsonLd = generateCategoryJsonLd(
category,
productData?.products || [],
shopConfig.baseUrl,
shopConfig
);
const combinedCategoryHead = categoryMetaTags + "\n" + categoryJsonLd;
const success = render(
categoryComponent,
location,
filename,
description,
categoryJsonLd,
combinedCategoryHead,
true
);
if (success) {

View File

@@ -1,3 +1,43 @@
/** Safe for double-quoted HTML attributes */
const escAttr = (str) =>
String(str ?? "")
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/</g, "&lt;");
/**
* 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];
@@ -173,5 +213,6 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
};
module.exports = {
generateCategoryMetaTags,
generateCategoryJsonLd,
};
};

View File

@@ -5,6 +5,7 @@ const {
} = require('./product.cjs');
const {
generateCategoryMetaTags,
generateCategoryJsonLd,
} = require('./category.cjs');
@@ -41,6 +42,7 @@ module.exports = {
generateProductJsonLd,
// Category functions
generateCategoryMetaTags,
generateCategoryJsonLd,
// Homepage functions