feat: Add generateCategoryMetaTags function for enhanced SEO in category pages and integrate it into prerender process
This commit is contained in:
@@ -125,6 +125,7 @@ const {
|
|||||||
const {
|
const {
|
||||||
generateProductMetaTags,
|
generateProductMetaTags,
|
||||||
generateProductJsonLd,
|
generateProductJsonLd,
|
||||||
|
generateCategoryMetaTags,
|
||||||
generateCategoryJsonLd,
|
generateCategoryJsonLd,
|
||||||
generateHomepageMetaTags,
|
generateHomepageMetaTags,
|
||||||
generateHomepageJsonLd,
|
generateHomepageJsonLd,
|
||||||
@@ -621,19 +622,25 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
const filename = `Kategorie/${category.seoName}`;
|
const filename = `Kategorie/${category.seoName}`;
|
||||||
const location = `/Kategorie/${category.seoName}`;
|
const location = `/Kategorie/${category.seoName}`;
|
||||||
const description = `Category "${category.name}" (ID: ${category.id})`;
|
const description = `Category "${category.name}" (ID: ${category.id})`;
|
||||||
|
const categoryMetaTags = generateCategoryMetaTags(
|
||||||
|
category,
|
||||||
|
shopConfig.baseUrl,
|
||||||
|
shopConfig
|
||||||
|
);
|
||||||
const categoryJsonLd = generateCategoryJsonLd(
|
const categoryJsonLd = generateCategoryJsonLd(
|
||||||
category,
|
category,
|
||||||
productData?.products || [],
|
productData?.products || [],
|
||||||
shopConfig.baseUrl,
|
shopConfig.baseUrl,
|
||||||
shopConfig
|
shopConfig
|
||||||
);
|
);
|
||||||
|
const combinedCategoryHead = categoryMetaTags + "\n" + categoryJsonLd;
|
||||||
|
|
||||||
const success = render(
|
const success = render(
|
||||||
categoryComponent,
|
categoryComponent,
|
||||||
location,
|
location,
|
||||||
filename,
|
filename,
|
||||||
description,
|
description,
|
||||||
categoryJsonLd,
|
combinedCategoryHead,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|||||||
@@ -1,3 +1,43 @@
|
|||||||
|
/** 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) => {
|
const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
||||||
// Category IDs to skip (seeds, plants, headshop items)
|
// 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];
|
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 = {
|
module.exports = {
|
||||||
|
generateCategoryMetaTags,
|
||||||
generateCategoryJsonLd,
|
generateCategoryJsonLd,
|
||||||
};
|
};
|
||||||
@@ -5,6 +5,7 @@ const {
|
|||||||
} = require('./product.cjs');
|
} = require('./product.cjs');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
generateCategoryMetaTags,
|
||||||
generateCategoryJsonLd,
|
generateCategoryJsonLd,
|
||||||
} = require('./category.cjs');
|
} = require('./category.cjs');
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ module.exports = {
|
|||||||
generateProductJsonLd,
|
generateProductJsonLd,
|
||||||
|
|
||||||
// Category functions
|
// Category functions
|
||||||
|
generateCategoryMetaTags,
|
||||||
generateCategoryJsonLd,
|
generateCategoryJsonLd,
|
||||||
|
|
||||||
// Homepage functions
|
// Homepage functions
|
||||||
|
|||||||
Reference in New Issue
Block a user