Compare commits
2 Commits
2ced182570
...
21d86565f1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21d86565f1 | ||
|
|
c503de3a11 |
@@ -7,41 +7,82 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
|||||||
return '';
|
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)
|
// Calculate price valid date (current date + 3 months)
|
||||||
const priceValidDate = new Date();
|
const priceValidDate = new Date();
|
||||||
priceValidDate.setMonth(priceValidDate.getMonth() + 3);
|
priceValidDate.setMonth(priceValidDate.getMonth() + 3);
|
||||||
const priceValidUntil = priceValidDate.toISOString().split("T")[0];
|
const priceValidUntil = priceValidDate.toISOString().split("T")[0];
|
||||||
|
|
||||||
const jsonLd = {
|
const id = {
|
||||||
"@context": "https://schema.org/",
|
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",
|
"@type": "CollectionPage",
|
||||||
name: category.name,
|
name: category.name,
|
||||||
url: categoryUrl,
|
url: categoryUrl,
|
||||||
description: `${category.name} - Entdecken Sie unsere Auswahl an hochwertigen Produkten`,
|
description: `${category.name} - Entdecken Sie unsere Auswahl an hochwertigen Produkten`,
|
||||||
breadcrumb: {
|
isPartOf: { "@id": id.website },
|
||||||
"@type": "BreadcrumbList",
|
breadcrumb: { "@id": id.breadcrumb },
|
||||||
itemListElement: [
|
|
||||||
{
|
|
||||||
"@type": "ListItem",
|
|
||||||
position: 1,
|
|
||||||
name: "Home",
|
|
||||||
item: baseUrl,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "ListItem",
|
|
||||||
position: 2,
|
|
||||||
name: category.name,
|
|
||||||
item: categoryUrl,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const graph = [businessNode, websiteNode, breadcrumbNode, collectionPageNode];
|
||||||
|
|
||||||
// Add product list if products are available
|
// Add product list if products are available
|
||||||
if (products && products.length > 0) {
|
if (products && products.length > 0) {
|
||||||
jsonLd.mainEntity = {
|
collectionPageNode.mainEntity = { "@id": id.itemList };
|
||||||
|
|
||||||
|
graph.push({
|
||||||
|
"@id": id.itemList,
|
||||||
"@type": "ItemList",
|
"@type": "ItemList",
|
||||||
numberOfItems: products.length,
|
numberOfItems: products.length,
|
||||||
itemListElement: products.slice(0, 20).map((product, index) => ({
|
itemListElement: products.slice(0, 20).map((product, index) => ({
|
||||||
@@ -50,13 +91,13 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
|||||||
item: {
|
item: {
|
||||||
"@type": "Product",
|
"@type": "Product",
|
||||||
name: product.name,
|
name: product.name,
|
||||||
url: `${baseUrl}/Artikel/${product.seoName}`,
|
url: `${root}/Artikel/${product.seoName}`,
|
||||||
image:
|
image:
|
||||||
product.pictureList && product.pictureList.trim()
|
product.pictureList && product.pictureList.trim()
|
||||||
? `${baseUrl}/assets/images/prod${product.pictureList
|
? `${root}/assets/images/prod${product.pictureList
|
||||||
.split(",")[0]
|
.split(",")[0]
|
||||||
.trim()}.avif`
|
.trim()}.avif`
|
||||||
: `${baseUrl}/assets/images/nopicture.jpg`,
|
: `${root}/assets/images/nopicture.jpg`,
|
||||||
description: product.description
|
description: product.description
|
||||||
? product.description.replace(/<[^>]*>/g, "").substring(0, 200)
|
? product.description.replace(/<[^>]*>/g, "").substring(0, 200)
|
||||||
: `${product.name} - Hochwertiges Growshop Produkt`,
|
: `${product.name} - Hochwertiges Growshop Produkt`,
|
||||||
@@ -67,22 +108,23 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
|||||||
},
|
},
|
||||||
offers: {
|
offers: {
|
||||||
"@type": "Offer",
|
"@type": "Offer",
|
||||||
url: `${baseUrl}/Artikel/${product.seoName}`,
|
url: `${root}/Artikel/${product.seoName}`,
|
||||||
price: product.price && !isNaN(product.price) ? product.price.toString() : "0.00",
|
price:
|
||||||
|
product.price && !isNaN(product.price)
|
||||||
|
? product.price.toString()
|
||||||
|
: "0.00",
|
||||||
priceCurrency: config.currency,
|
priceCurrency: config.currency,
|
||||||
priceValidUntil: priceValidUntil,
|
priceValidUntil: priceValidUntil,
|
||||||
availability: product.available
|
availability: product.available
|
||||||
? "https://schema.org/InStock"
|
? "https://schema.org/InStock"
|
||||||
: "https://schema.org/OutOfStock",
|
: "https://schema.org/OutOfStock",
|
||||||
seller: {
|
seller: { "@id": id.business },
|
||||||
"@type": "Organization",
|
|
||||||
name: config.brandName,
|
|
||||||
},
|
|
||||||
itemCondition: "https://schema.org/NewCondition",
|
itemCondition: "https://schema.org/NewCondition",
|
||||||
hasMerchantReturnPolicy: {
|
hasMerchantReturnPolicy: {
|
||||||
"@type": "MerchantReturnPolicy",
|
"@type": "MerchantReturnPolicy",
|
||||||
applicableCountry: "DE",
|
applicableCountry: "DE",
|
||||||
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
|
returnPolicyCategory:
|
||||||
|
"https://schema.org/MerchantReturnFiniteReturnWindow",
|
||||||
merchantReturnDays: 14,
|
merchantReturnDays: 14,
|
||||||
returnMethod: "https://schema.org/ReturnByMail",
|
returnMethod: "https://schema.org/ReturnByMail",
|
||||||
returnFees: "https://schema.org/FreeReturn",
|
returnFees: "https://schema.org/FreeReturn",
|
||||||
@@ -91,7 +133,7 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
|||||||
"@type": "OfferShippingDetails",
|
"@type": "OfferShippingDetails",
|
||||||
shippingRate: {
|
shippingRate: {
|
||||||
"@type": "MonetaryAmount",
|
"@type": "MonetaryAmount",
|
||||||
value: 5.90,
|
value: 5.9,
|
||||||
currency: "EUR",
|
currency: "EUR",
|
||||||
},
|
},
|
||||||
shippingDestination: {
|
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(
|
return `<script type="application/ld+json">${JSON.stringify(
|
||||||
jsonLd
|
categoryGraph
|
||||||
)}</script>`;
|
)}</script>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,177 +36,198 @@ const generateHomepageJsonLd = (baseUrl, config, categories = []) => {
|
|||||||
const canonicalUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
const canonicalUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
||||||
const logoUrl = `${canonicalUrl}${config.images.logo}`;
|
const logoUrl = `${canonicalUrl}${config.images.logo}`;
|
||||||
|
|
||||||
const websiteJsonLd = {
|
const id = {
|
||||||
"@context": "https://schema.org/",
|
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",
|
"@type": "WebSite",
|
||||||
name: config.brandName,
|
name: config.brandName,
|
||||||
url: canonicalUrl,
|
url: canonicalUrl,
|
||||||
description: config.descriptions.de.long,
|
description: config.descriptions.de.long,
|
||||||
publisher: {
|
publisher: { "@id": id.business },
|
||||||
"@type": "Organization",
|
|
||||||
name: config.brandName,
|
|
||||||
url: canonicalUrl,
|
|
||||||
logo: {
|
|
||||||
"@type": "ImageObject",
|
|
||||||
url: logoUrl,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
potentialAction: {
|
potentialAction: {
|
||||||
"@type": "SearchAction",
|
"@type": "SearchAction",
|
||||||
target: `${canonicalUrl}/search?q={search_term_string}`,
|
target: `${canonicalUrl}/search?q={search_term_string}`,
|
||||||
query: "required name=search_term_string"
|
query: "required name=search_term_string",
|
||||||
},
|
},
|
||||||
mainEntity: {
|
mainEntity: { "@id": id.sitemapPage },
|
||||||
"@type": "WebPage",
|
sameAs: [],
|
||||||
name: "Sitemap",
|
|
||||||
url: `${canonicalUrl}/sitemap`,
|
|
||||||
description: "Vollständige Sitemap mit allen Kategorien und Seiten",
|
|
||||||
},
|
|
||||||
sameAs: [
|
|
||||||
// Add your social media URLs here if available
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Organization/LocalBusiness Schema for rich results
|
const faqMainEntity = [
|
||||||
const organizationJsonLd = {
|
{
|
||||||
"@context": "https://schema.org",
|
"@type": "Question",
|
||||||
"@type": "LocalBusiness",
|
name: "Welche Zahlungsmethoden akzeptiert GrowHeads?",
|
||||||
"name": config.brandName,
|
acceptedAnswer: {
|
||||||
"alternateName": config.siteName,
|
"@type": "Answer",
|
||||||
"description": config.descriptions.de.long,
|
text: "Wir akzeptieren Kreditkarten, PayPal, Banküberweisung, Nachnahme und Barzahlung bei Abholung in unserem Laden in Dresden.",
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
"@type": "ContactPoint",
|
{
|
||||||
"email": "service@growheads.de",
|
"@type": "Question",
|
||||||
"contactType": "customer service",
|
name: "Liefert GrowHeads deutschlandweit?",
|
||||||
"availableLanguage": "German"
|
acceptedAnswer: {
|
||||||
}
|
"@type": "Answer",
|
||||||
],
|
text: "Ja, wir liefern deutschlandweit. Zusätzlich haben wir einen Laden in Dresden (Trachenberger Strasse 14) für lokale Kunden.",
|
||||||
"sameAs": [
|
},
|
||||||
// Add social media URLs when available
|
},
|
||||||
// "https://www.facebook.com/growheads",
|
{
|
||||||
// "https://www.instagram.com/growheads"
|
"@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 faqNode = {
|
||||||
const faqJsonLd = {
|
"@id": id.faq,
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "FAQPage",
|
"@type": "FAQPage",
|
||||||
"mainEntity": [
|
url: canonicalUrl,
|
||||||
{
|
publisher: { "@id": id.business },
|
||||||
"@type": "Question",
|
isPartOf: { "@id": id.website },
|
||||||
"name": "Welche Zahlungsmethoden akzeptiert GrowHeads?",
|
mainEntity: faqMainEntity,
|
||||||
"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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate ItemList for all categories (more appropriate for homepage)
|
const filteredCategories = categories.filter((c) => c.seoName);
|
||||||
const categoriesListJsonLd = {
|
|
||||||
"@context": "https://schema.org",
|
const graph = [
|
||||||
"@type": "ItemList",
|
organizationNode,
|
||||||
"name": "Produktkategorien",
|
websiteNode,
|
||||||
"description": "Alle verfügbaren Produktkategorien in unserem Online-Shop",
|
sitemapWebPageNode,
|
||||||
"numberOfItems": categories.filter(category => category.seoName).length,
|
faqNode,
|
||||||
"itemListElement": categories
|
];
|
||||||
.filter(category => category.seoName) // Only include categories with seoName
|
|
||||||
.map((category, index) => ({
|
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",
|
"@type": "ListItem",
|
||||||
"position": index + 1,
|
position: index + 1,
|
||||||
"item": {
|
item: {
|
||||||
"@type": "Thing",
|
"@type": "Thing",
|
||||||
"name": category.name,
|
name: category.name,
|
||||||
"url": `${canonicalUrl}/Kategorie/${category.seoName}`
|
url: `${canonicalUrl}/Kategorie/${category.seoName}`,
|
||||||
}
|
},
|
||||||
}))
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const homepageGraph = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@graph": graph,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return all JSON-LD scripts
|
return `<script type="application/ld+json">${JSON.stringify(
|
||||||
const websiteScript = `<script type="application/ld+json">${JSON.stringify(websiteJsonLd)}</script>`;
|
homepageGraph
|
||||||
const organizationScript = `<script type="application/ld+json">${JSON.stringify(organizationJsonLd)}</script>`;
|
)}</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 : '');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -68,14 +68,15 @@ const generateProductMetaTags = (product, baseUrl, config) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateProductJsonLd = (product, baseUrl, config, categoryInfo = null) => {
|
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 =
|
const pictureFirstId =
|
||||||
product.pictureList && product.pictureList.trim()
|
product.pictureList && product.pictureList.trim()
|
||||||
? product.pictureList.split(",")[0].trim()
|
? product.pictureList.split(",")[0].trim()
|
||||||
: null;
|
: null;
|
||||||
const imageUrl = pictureFirstId
|
const imageUrl = pictureFirstId
|
||||||
? `${baseUrl}/assets/images/prod${pictureFirstId}.avif`
|
? `${root}/assets/images/prod${pictureFirstId}.avif`
|
||||||
: `${baseUrl}/assets/images/nopicture.jpg`;
|
: `${root}/assets/images/nopicture.jpg`;
|
||||||
|
|
||||||
// Clean description for JSON-LD (remove HTML tags)
|
// Clean description for JSON-LD (remove HTML tags)
|
||||||
const cleanDescription = product.description
|
const cleanDescription = product.description
|
||||||
@@ -86,8 +87,87 @@ const generateProductJsonLd = (product, baseUrl, config, categoryInfo = null) =>
|
|||||||
const priceValidDate = new Date();
|
const priceValidDate = new Date();
|
||||||
priceValidDate.setMonth(priceValidDate.getMonth() + 3);
|
priceValidDate.setMonth(priceValidDate.getMonth() + 3);
|
||||||
|
|
||||||
const productJsonLd = {
|
const id = {
|
||||||
"@context": "https://schema.org/",
|
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",
|
"@type": "Product",
|
||||||
name: product.name,
|
name: product.name,
|
||||||
image: [imageUrl],
|
image: [imageUrl],
|
||||||
@@ -98,95 +178,65 @@ const generateProductJsonLd = (product, baseUrl, config, categoryInfo = null) =>
|
|||||||
"@type": "Brand",
|
"@type": "Brand",
|
||||||
name: product.manufacturer || "Unknown",
|
name: product.manufacturer || "Unknown",
|
||||||
},
|
},
|
||||||
offers: {
|
offers: 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: {
|
|
||||||
"@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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const productScript = `<script type="application/ld+json">${JSON.stringify(
|
const hasBreadcrumb =
|
||||||
productJsonLd
|
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>`;
|
)}</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 = {
|
module.exports = {
|
||||||
|
|||||||
Reference in New Issue
Block a user