refactor: Enhance JSON-LD structure in category and product generation functions for improved SEO and consistency across URLs
This commit is contained in:
@@ -7,41 +7,82 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
||||
return '';
|
||||
}
|
||||
|
||||
const categoryUrl = `${baseUrl}/Kategorie/${category.seoName}`;
|
||||
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 jsonLd = {
|
||||
"@context": "https://schema.org/",
|
||||
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`,
|
||||
breadcrumb: {
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
position: 1,
|
||||
name: "Home",
|
||||
item: baseUrl,
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
position: 2,
|
||||
name: category.name,
|
||||
item: categoryUrl,
|
||||
},
|
||||
],
|
||||
},
|
||||
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) {
|
||||
jsonLd.mainEntity = {
|
||||
collectionPageNode.mainEntity = { "@id": id.itemList };
|
||||
|
||||
graph.push({
|
||||
"@id": id.itemList,
|
||||
"@type": "ItemList",
|
||||
numberOfItems: products.length,
|
||||
itemListElement: products.slice(0, 20).map((product, index) => ({
|
||||
@@ -50,14 +91,14 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
||||
item: {
|
||||
"@type": "Product",
|
||||
name: product.name,
|
||||
url: `${baseUrl}/Artikel/${product.seoName}`,
|
||||
url: `${root}/Artikel/${product.seoName}`,
|
||||
image:
|
||||
product.pictureList && product.pictureList.trim()
|
||||
? `${baseUrl}/assets/images/prod${product.pictureList
|
||||
? `${root}/assets/images/prod${product.pictureList
|
||||
.split(",")[0]
|
||||
.trim()}.avif`
|
||||
: `${baseUrl}/assets/images/nopicture.jpg`,
|
||||
description: product.description
|
||||
: `${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,
|
||||
@@ -67,22 +108,23 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
||||
},
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
url: `${baseUrl}/Artikel/${product.seoName}`,
|
||||
price: product.price && !isNaN(product.price) ? product.price.toString() : "0.00",
|
||||
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: {
|
||||
"@type": "Organization",
|
||||
name: config.brandName,
|
||||
},
|
||||
seller: { "@id": id.business },
|
||||
itemCondition: "https://schema.org/NewCondition",
|
||||
hasMerchantReturnPolicy: {
|
||||
"@type": "MerchantReturnPolicy",
|
||||
applicableCountry: "DE",
|
||||
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
|
||||
returnPolicyCategory:
|
||||
"https://schema.org/MerchantReturnFiniteReturnWindow",
|
||||
merchantReturnDays: 14,
|
||||
returnMethod: "https://schema.org/ReturnByMail",
|
||||
returnFees: "https://schema.org/FreeReturn",
|
||||
@@ -91,7 +133,7 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
||||
"@type": "OfferShippingDetails",
|
||||
shippingRate: {
|
||||
"@type": "MonetaryAmount",
|
||||
value: 5.90,
|
||||
value: 5.9,
|
||||
currency: "EUR",
|
||||
},
|
||||
shippingDestination: {
|
||||
@@ -117,11 +159,16 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
||||
},
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const categoryGraph = {
|
||||
"@context": "https://schema.org",
|
||||
"@graph": graph,
|
||||
};
|
||||
|
||||
return `<script type="application/ld+json">${JSON.stringify(
|
||||
jsonLd
|
||||
categoryGraph
|
||||
)}</script>`;
|
||||
};
|
||||
|
||||
|
||||
@@ -36,177 +36,198 @@ const generateHomepageJsonLd = (baseUrl, config, categories = []) => {
|
||||
const canonicalUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
||||
const logoUrl = `${canonicalUrl}${config.images.logo}`;
|
||||
|
||||
const websiteJsonLd = {
|
||||
"@context": "https://schema.org/",
|
||||
const id = {
|
||||
business: `${canonicalUrl}#business`,
|
||||
website: `${canonicalUrl}#website`,
|
||||
faq: `${canonicalUrl}#faq`,
|
||||
categoryList: `${canonicalUrl}#category-list`,
|
||||
sitemapPage: `${canonicalUrl}/sitemap#webpage`,
|
||||
};
|
||||
|
||||
const organizationNode = {
|
||||
"@id": id.business,
|
||||
"@type": ["GardenStore", "LocalBusiness", "Organization"],
|
||||
name: config.brandName,
|
||||
alternateName: config.siteName,
|
||||
description: config.descriptions.de.long,
|
||||
url: canonicalUrl,
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: logoUrl,
|
||||
},
|
||||
image: {
|
||||
"@type": "ImageObject",
|
||||
url: logoUrl,
|
||||
},
|
||||
telephone: "015208491860",
|
||||
email: "service@growheads.de",
|
||||
address: {
|
||||
"@type": "PostalAddress",
|
||||
streetAddress: "Trachenberger Strasse 14",
|
||||
addressLocality: "Dresden",
|
||||
postalCode: "01129",
|
||||
addressCountry: "DE",
|
||||
addressRegion: "Sachsen",
|
||||
},
|
||||
geo: {
|
||||
"@type": "GeoCoordinates",
|
||||
latitude: "51.083675",
|
||||
longitude: "13.727215",
|
||||
},
|
||||
openingHours: [
|
||||
"Mo-Fr 10:00:00-20:00:00",
|
||||
"Sa 11:00:00-19:00:00",
|
||||
],
|
||||
paymentAccepted: "Cash, Credit Card, PayPal, Bank Transfer",
|
||||
currenciesAccepted: "EUR",
|
||||
priceRange: "€€",
|
||||
areaServed: {
|
||||
"@type": "Country",
|
||||
name: "Germany",
|
||||
},
|
||||
contactPoint: [
|
||||
{
|
||||
"@type": "ContactPoint",
|
||||
telephone: "015208491860",
|
||||
contactType: "customer service",
|
||||
availableLanguage: "German",
|
||||
hoursAvailable: {
|
||||
"@type": "OpeningHoursSpecification",
|
||||
dayOfWeek: [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
],
|
||||
opens: "10:00:00",
|
||||
closes: "20:00:00",
|
||||
},
|
||||
},
|
||||
{
|
||||
"@type": "ContactPoint",
|
||||
email: "service@growheads.de",
|
||||
contactType: "customer service",
|
||||
availableLanguage: "German",
|
||||
},
|
||||
],
|
||||
sameAs: [],
|
||||
};
|
||||
|
||||
const sitemapWebPageNode = {
|
||||
"@id": id.sitemapPage,
|
||||
"@type": "WebPage",
|
||||
name: "Sitemap",
|
||||
url: `${canonicalUrl}/sitemap`,
|
||||
description: "Vollständige Sitemap mit allen Kategorien und Seiten",
|
||||
isPartOf: { "@id": id.website },
|
||||
};
|
||||
|
||||
const websiteNode = {
|
||||
"@id": id.website,
|
||||
"@type": "WebSite",
|
||||
name: config.brandName,
|
||||
url: canonicalUrl,
|
||||
description: config.descriptions.de.long,
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: config.brandName,
|
||||
url: canonicalUrl,
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: logoUrl,
|
||||
},
|
||||
},
|
||||
publisher: { "@id": id.business },
|
||||
potentialAction: {
|
||||
"@type": "SearchAction",
|
||||
target: `${canonicalUrl}/search?q={search_term_string}`,
|
||||
query: "required name=search_term_string"
|
||||
query: "required name=search_term_string",
|
||||
},
|
||||
mainEntity: {
|
||||
"@type": "WebPage",
|
||||
name: "Sitemap",
|
||||
url: `${canonicalUrl}/sitemap`,
|
||||
description: "Vollständige Sitemap mit allen Kategorien und Seiten",
|
||||
},
|
||||
sameAs: [
|
||||
// Add your social media URLs here if available
|
||||
],
|
||||
mainEntity: { "@id": id.sitemapPage },
|
||||
sameAs: [],
|
||||
};
|
||||
|
||||
// Store entity: specific subtype (GardenStore) + LocalBusiness + Organization for clear merchant categorization
|
||||
const organizationJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": ["GardenStore", "LocalBusiness", "Organization"],
|
||||
"name": config.brandName,
|
||||
"alternateName": config.siteName,
|
||||
"description": config.descriptions.de.long,
|
||||
"url": canonicalUrl,
|
||||
"logo": logoUrl,
|
||||
"image": logoUrl,
|
||||
"telephone": "015208491860",
|
||||
"email": "service@growheads.de",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": "Trachenberger Strasse 14",
|
||||
"addressLocality": "Dresden",
|
||||
"postalCode": "01129",
|
||||
"addressCountry": "DE",
|
||||
"addressRegion": "Sachsen"
|
||||
},
|
||||
"geo": {
|
||||
"@type": "GeoCoordinates",
|
||||
"latitude": "51.083675",
|
||||
"longitude": "13.727215"
|
||||
},
|
||||
"openingHours": [
|
||||
"Mo-Fr 10:00:00-20:00:00",
|
||||
"Sa 11:00:00-19:00:00"
|
||||
],
|
||||
"paymentAccepted": "Cash, Credit Card, PayPal, Bank Transfer",
|
||||
"currenciesAccepted": "EUR",
|
||||
"priceRange": "€€",
|
||||
"areaServed": {
|
||||
"@type": "Country",
|
||||
"name": "Germany"
|
||||
},
|
||||
"contactPoint": [
|
||||
{
|
||||
"@type": "ContactPoint",
|
||||
"telephone": "015208491860",
|
||||
"contactType": "customer service",
|
||||
"availableLanguage": "German",
|
||||
"hoursAvailable": {
|
||||
"@type": "OpeningHoursSpecification",
|
||||
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
||||
"opens": "10:00:00",
|
||||
"closes": "20:00:00"
|
||||
}
|
||||
const faqMainEntity = [
|
||||
{
|
||||
"@type": "Question",
|
||||
name: "Welche Zahlungsmethoden akzeptiert GrowHeads?",
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: "Wir akzeptieren Kreditkarten, PayPal, Banküberweisung, Nachnahme und Barzahlung bei Abholung in unserem Laden in Dresden.",
|
||||
},
|
||||
{
|
||||
"@type": "ContactPoint",
|
||||
"email": "service@growheads.de",
|
||||
"contactType": "customer service",
|
||||
"availableLanguage": "German"
|
||||
}
|
||||
],
|
||||
"sameAs": [
|
||||
// Add social media URLs when available
|
||||
// "https://www.facebook.com/growheads",
|
||||
// "https://www.instagram.com/growheads"
|
||||
]
|
||||
};
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
name: "Liefert GrowHeads deutschlandweit?",
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: "Ja, wir liefern deutschlandweit. Zusätzlich haben wir einen Laden in Dresden (Trachenberger Strasse 14) für lokale Kunden.",
|
||||
},
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
name: "Welche Produkte bietet GrowHeads?",
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: "Wir bieten ein komplettes Sortiment für den Indoor-Anbau: Beleuchtung, Belüftung, Dünger, Töpfe, Zelte, Messgeräte und vieles mehr für professionelle Zuchtanlagen.",
|
||||
},
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
name: "Hat GrowHeads einen physischen Laden?",
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: "Ja, unser Laden befindet sich in Dresden, Trachenberger Strasse 14. Öffnungszeiten: Mo-Fr 10-20 Uhr, Sa 11-19 Uhr. Sie können auch online bestellen.",
|
||||
},
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
name: "Bietet GrowHeads Beratung zum Indoor-Anbau?",
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: "Ja, unser erfahrenes Team berät Sie gerne zu allen Aspekten des Indoor-Anbaus. Kontaktieren Sie uns telefonisch unter 015208491860 oder besuchen Sie unseren Laden.",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// FAQPage Schema for common questions
|
||||
const faqJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
const faqNode = {
|
||||
"@id": id.faq,
|
||||
"@type": "FAQPage",
|
||||
"mainEntity": [
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "Welche Zahlungsmethoden akzeptiert GrowHeads?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Wir akzeptieren Kreditkarten, PayPal, Banküberweisung, Nachnahme und Barzahlung bei Abholung in unserem Laden in Dresden."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "Liefert GrowHeads deutschlandweit?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Ja, wir liefern deutschlandweit. Zusätzlich haben wir einen Laden in Dresden (Trachenberger Strasse 14) für lokale Kunden."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "Welche Produkte bietet GrowHeads?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Wir bieten ein komplettes Sortiment für den Indoor-Anbau: Beleuchtung, Belüftung, Dünger, Töpfe, Zelte, Messgeräte und vieles mehr für professionelle Zuchtanlagen."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "Hat GrowHeads einen physischen Laden?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Ja, unser Laden befindet sich in Dresden, Trachenberger Strasse 14. Öffnungszeiten: Mo-Fr 10-20 Uhr, Sa 11-19 Uhr. Sie können auch online bestellen."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "Bietet GrowHeads Beratung zum Indoor-Anbau?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Ja, unser erfahrenes Team berät Sie gerne zu allen Aspekten des Indoor-Anbaus. Kontaktieren Sie uns telefonisch unter 015208491860 oder besuchen Sie unseren Laden."
|
||||
}
|
||||
}
|
||||
]
|
||||
url: canonicalUrl,
|
||||
publisher: { "@id": id.business },
|
||||
isPartOf: { "@id": id.website },
|
||||
mainEntity: faqMainEntity,
|
||||
};
|
||||
|
||||
// Generate ItemList for all categories (more appropriate for homepage)
|
||||
const categoriesListJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ItemList",
|
||||
"name": "Produktkategorien",
|
||||
"description": "Alle verfügbaren Produktkategorien in unserem Online-Shop",
|
||||
"numberOfItems": categories.filter(category => category.seoName).length,
|
||||
"itemListElement": categories
|
||||
.filter(category => category.seoName) // Only include categories with seoName
|
||||
.map((category, index) => ({
|
||||
const filteredCategories = categories.filter((c) => c.seoName);
|
||||
|
||||
const graph = [
|
||||
organizationNode,
|
||||
websiteNode,
|
||||
sitemapWebPageNode,
|
||||
faqNode,
|
||||
];
|
||||
|
||||
if (filteredCategories.length > 0) {
|
||||
graph.push({
|
||||
"@id": id.categoryList,
|
||||
"@type": "ItemList",
|
||||
name: "Produktkategorien",
|
||||
description: "Alle verfügbaren Produktkategorien in unserem Online-Shop",
|
||||
numberOfItems: filteredCategories.length,
|
||||
isPartOf: { "@id": id.website },
|
||||
itemListElement: filteredCategories.map((category, index) => ({
|
||||
"@type": "ListItem",
|
||||
"position": index + 1,
|
||||
"item": {
|
||||
position: index + 1,
|
||||
item: {
|
||||
"@type": "Thing",
|
||||
"name": category.name,
|
||||
"url": `${canonicalUrl}/Kategorie/${category.seoName}`
|
||||
}
|
||||
}))
|
||||
name: category.name,
|
||||
url: `${canonicalUrl}/Kategorie/${category.seoName}`,
|
||||
},
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
const homepageGraph = {
|
||||
"@context": "https://schema.org",
|
||||
"@graph": graph,
|
||||
};
|
||||
|
||||
// Return all JSON-LD scripts
|
||||
const websiteScript = `<script type="application/ld+json">${JSON.stringify(websiteJsonLd)}</script>`;
|
||||
const organizationScript = `<script type="application/ld+json">${JSON.stringify(organizationJsonLd)}</script>`;
|
||||
const faqScript = `<script type="application/ld+json">${JSON.stringify(faqJsonLd)}</script>`;
|
||||
const categoriesScript = categories.length > 0
|
||||
? `<script type="application/ld+json">${JSON.stringify(categoriesListJsonLd)}</script>`
|
||||
: '';
|
||||
|
||||
return websiteScript + '\n' + organizationScript + '\n' + faqScript + (categoriesScript ? '\n' + categoriesScript : '');
|
||||
return `<script type="application/ld+json">${JSON.stringify(
|
||||
homepageGraph
|
||||
)}</script>`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -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