feat: migration von directus zu payloadcms
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m19s
Build & Deploy / 🧪 QA (push) Failing after 3m32s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Failing after 7m51s
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ♿ WCAG (push) Has been skipped
Build & Deploy / 🛡️ Quality Gates (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 10s

This commit is contained in:
2026-02-24 19:25:43 +01:00
parent 2bac8d6e8a
commit f7aa880d9f
91 changed files with 1010 additions and 1028 deletions

View File

@@ -29,6 +29,10 @@ export async function generateMetadata(props: {
const baseUrl = process.env.CI ? 'http://klz.localhost' : SITE_URL;
return {
title: {
template: '%s | KLZ Cables',
default: 'KLZ Cables | Ihr Partner für Kabel & Leitungen',
},
metadataBase: new URL(baseUrl),
manifest: '/manifest.webmanifest',
alternates: {
@@ -128,7 +132,11 @@ export default async function Layout(props: {
const feedbackEnabled = process.env.NEXT_PUBLIC_FEEDBACK_ENABLED === 'true';
return (
<html lang={safeLocale} className={`scroll-smooth overflow-x-hidden ${inter.variable}`}>
<html
lang={safeLocale}
className={`scroll-smooth overflow-x-hidden ${inter.variable}`}
data-scroll-behavior="smooth"
>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />

View File

@@ -11,9 +11,19 @@ export default function NotFound() {
const { trackEvent } = useAnalytics();
useEffect(() => {
const errorUrl = typeof window !== 'undefined' ? window.location.pathname : 'unknown';
trackEvent(AnalyticsEvents.ERROR, {
type: '404_not_found',
path: typeof window !== 'undefined' ? window.location.pathname : 'unknown',
path: errorUrl,
});
// Explicitly send the 404 to Sentry so we have visibility into broken links
import('@sentry/nextjs').then((Sentry) => {
Sentry.withScope((scope) => {
scope.setTag('status_code', '404');
scope.setTag('path', errorUrl);
Sentry.captureMessage(`Route Not Found: ${errorUrl}`, 'warning');
});
});
}, [trackEvent]);

View File

@@ -60,17 +60,6 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
'x-default': `${SITE_URL}/en/${await mapFileSlugToTranslated('products', 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
},
},
openGraph: {
title: `${categoryTitle} | KLZ Cables`,
description: categoryDesc,
url: `${SITE_URL}/${locale}/products/${productSlug}`,
images: getProductOGImageMetadata(fileSlug, categoryTitle, locale),
},
twitter: {
card: 'summary_large_image',
title: `${categoryTitle} | KLZ Cables`,
description: categoryDesc,
},
};
}
@@ -89,7 +78,7 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
},
},
openGraph: {
title: `${product.frontmatter.title} | KLZ Cables`,
title: product.frontmatter.title,
description: product.frontmatter.description,
type: 'website',
url: `${SITE_URL}/${locale}/products/${slug.join('/')}`,
@@ -97,14 +86,12 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
},
twitter: {
card: 'summary_large_image',
title: `${product.frontmatter.title} | KLZ Cables`,
title: product.frontmatter.title,
description: product.frontmatter.description,
},
};
}
// ... the rest of the file layout ...
export default async function ProductPage({ params }: ProductPageProps) {
const { locale, slug } = await params;
setRequestLocale(locale);
@@ -121,7 +108,6 @@ export default async function ProductPage({ params }: ProductPageProps) {
const fileSlug = await mapSlugToFileSlug(productSlug, locale);
if (categories.includes(fileSlug)) {
// (Skipping category page block, same as before)
const allProducts = await getAllProducts(locale);
const categoryKey = fileSlug
.replace(/-cables$/, '')
@@ -264,6 +250,37 @@ export default async function ProductPage({ params }: ProductPageProps) {
? t(`categories.${categoryKey}.title`)
: categoryFileSlug;
// Split content into Description and Technical Data
const rootChildren = product.content?.root?.children || [];
const technicalBlocks = rootChildren.filter(
(node: any) =>
node.type === 'block' &&
(node.fields?.blockType === 'productTabs' ||
node.fields?.blockType === 'productTechnicalData'),
);
const descriptionChildren = rootChildren.filter(
(node: any) =>
!(
node.type === 'block' &&
(node.fields?.blockType === 'productTabs' ||
node.fields?.blockType === 'productTechnicalData')
),
);
const descriptionContent = {
root: {
...product.content.root,
children: descriptionChildren,
},
};
const technicalContent = {
root: {
...product.content.root,
children: technicalBlocks,
},
};
const sidebar = (
<ProductSidebar
productName={product.frontmatter.title}
@@ -351,6 +368,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
src={product.frontmatter.images[0]}
alt={product.frontmatter.title}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-contain transition-transform duration-1000 hover:scale-105"
priority
/>
@@ -369,6 +387,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
src={img}
alt=""
fill
sizes="128px"
className="object-contain p-4 transition-transform duration-700 group-hover:scale-110"
/>
</div>
@@ -379,64 +398,74 @@ export default async function ProductPage({ params }: ProductPageProps) {
</div>
)}
<div className="relative">
<div className="w-full">
{/* Main Content Area */}
<div className="max-w-none prose prose-lg mt-8">
<PayloadRichText data={product.content} />
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20">
{/* Description Area Next to Sidebar */}
<div className="lg:col-span-8">
<div className="max-w-none prose prose-primary prose-lg md:prose-xl mb-16 pb-16 border-b border-neutral-dark/5">
<PayloadRichText data={descriptionContent} />
</div>
{/* Datasheet Download Section - Only for Medium Voltage for now */}
{categoryFileSlug === 'medium-voltage-cables' && datasheetPath && (
<div className="mt-24 pt-24 border-t-2 border-neutral-dark/5">
<div className="mb-12">
<h2 className="text-3xl md:text-4xl font-black text-primary tracking-tighter uppercase mb-4">
{t('downloadDatasheet')}
</h2>
<div className="h-1.5 w-24 bg-accent rounded-full" />
</div>
<DatasheetDownload datasheetPath={datasheetPath} />
</div>
)}
{/* Structured Data */}
<JsonLd
id={`jsonld-${product.slug}`}
data={
{
'@context': 'https://schema.org',
'@type': 'Product',
name: product.frontmatter.title,
description: product.frontmatter.description,
sku: product.frontmatter.sku || product.slug.toUpperCase(),
image: product.frontmatter.images?.[0]
? `${SITE_URL}${product.frontmatter.images[0]}`
: undefined,
brand: {
'@type': 'Brand',
name: 'KLZ Cables',
},
offers: {
'@type': 'Offer',
availability: 'https://schema.org/InStock',
priceCurrency: 'EUR',
url: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
itemCondition: 'https://schema.org/NewCondition',
},
additionalProperty: technicalItems.map((item: any) => ({
'@type': 'PropertyValue',
name: item.label,
value: item.value,
})),
category: product.frontmatter.categories.join(', '),
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
},
} as any
}
/>
</div>
{/* Sidebar Column */}
<div className="lg:col-span-4 lg:sticky lg:top-32 h-fit">{sidebar}</div>
</div>
{/* Full-width Technical Data Below */}
<div className="mt-16 pt-16 border-t-0">
<div className="max-w-none prose prose-primary prose-lg md:prose-xl">
<PayloadRichText data={technicalContent} />
</div>
{/* Datasheet Download Section */}
{categoryFileSlug === 'medium-voltage-cables' && datasheetPath && (
<div className="mt-16 pt-16 border-t-2 border-neutral-dark/5">
<div className="mb-8">
<h2 className="text-3xl md:text-4xl font-black text-primary tracking-tighter uppercase mb-4">
{t('downloadDatasheet')}
</h2>
<div className="h-1.5 w-24 bg-accent rounded-full" />
</div>
<DatasheetDownload datasheetPath={datasheetPath} />
</div>
)}
{/* Structured Data (Hidden) */}
<JsonLd
id={`jsonld-${product.slug}`}
data={
{
'@context': 'https://schema.org',
'@type': 'Product',
name: product.frontmatter.title,
description: product.frontmatter.description,
sku: product.frontmatter.sku || product.slug.toUpperCase(),
image: product.frontmatter.images?.[0]
? `${SITE_URL}${product.frontmatter.images[0]}`
: undefined,
brand: {
'@type': 'Brand',
name: 'KLZ Cables',
},
offers: {
'@type': 'Offer',
availability: 'https://schema.org/InStock',
priceCurrency: 'EUR',
url: `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
itemCondition: 'https://schema.org/NewCondition',
},
additionalProperty: technicalItems.map((item: any) => ({
'@type': 'PropertyValue',
name: item.label,
value: item.value,
})),
category: product.frontmatter.categories.join(', '),
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `${SITE_URL}/${locale}/${await mapFileSlugToTranslated('products', locale)}/${slug.join('/')}`,
},
} as any
}
/>
</div>
{/* Related Products Section */}