144 lines
4.7 KiB
TypeScript
144 lines
4.7 KiB
TypeScript
import { getSiteInfo } from '@/lib/i18n';
|
|
import { ReactElement } from 'react';
|
|
|
|
interface SEOProps {
|
|
title: string;
|
|
description?: string;
|
|
locale?: 'en' | 'de';
|
|
path?: string;
|
|
type?: 'website' | 'article' | 'product';
|
|
publishedTime?: string;
|
|
modifiedTime?: string;
|
|
authors?: string[];
|
|
images?: string[];
|
|
}
|
|
|
|
export function SEO({
|
|
title,
|
|
description,
|
|
locale = 'en',
|
|
path = '/',
|
|
type = 'website',
|
|
publishedTime,
|
|
modifiedTime,
|
|
authors,
|
|
images
|
|
}: SEOProps): ReactElement {
|
|
const site = getSiteInfo();
|
|
const fullTitle = title === 'Home' ? site.title : `${title} | ${site.title}`;
|
|
const fullDescription = description || site.description;
|
|
const canonicalUrl = `${site.baseUrl}${path}`;
|
|
|
|
// Generate alternate URLs
|
|
const alternateLocale = locale === 'en' ? 'de' : 'en';
|
|
const alternatePath = path === '/' ? '' : path;
|
|
const alternateUrl = `${site.baseUrl}/${alternateLocale}${alternatePath}`;
|
|
|
|
// Open Graph images
|
|
const ogImages = images && images.length > 0
|
|
? images
|
|
: [`${site.baseUrl}/og-image.jpg`];
|
|
|
|
return (
|
|
<>
|
|
{/* Basic Meta Tags */}
|
|
<title>{fullTitle}</title>
|
|
<meta name="description" content={fullDescription} />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta name="robots" content="index, follow" />
|
|
|
|
{/* Canonical URL */}
|
|
<link rel="canonical" href={canonicalUrl} />
|
|
|
|
{/* Alternate Languages */}
|
|
<link rel="alternate" hrefLang={locale} href={canonicalUrl} />
|
|
<link rel="alternate" hrefLang={alternateLocale} href={alternateUrl} />
|
|
<link rel="alternate" hrefLang="x-default" href={`${site.baseUrl}${alternatePath}`} />
|
|
|
|
{/* Open Graph */}
|
|
<meta property="og:type" content={type} />
|
|
<meta property="og:title" content={fullTitle} />
|
|
<meta property="og:description" content={fullDescription} />
|
|
<meta property="og:url" content={canonicalUrl} />
|
|
<meta property="og:locale" content={locale === 'en' ? 'en_US' : 'de_DE'} />
|
|
<meta property="og:site_name" content={site.title} />
|
|
|
|
{ogImages.map((image, index) => (
|
|
<meta key={index} property="og:image" content={image} />
|
|
))}
|
|
|
|
{publishedTime && (
|
|
<meta property="article:published_time" content={publishedTime} />
|
|
)}
|
|
|
|
{modifiedTime && (
|
|
<meta property="article:modified_time" content={modifiedTime} />
|
|
)}
|
|
|
|
{authors && authors.length > 0 && (
|
|
<meta property="article:author" content={authors.join(', ')} />
|
|
)}
|
|
|
|
{/* Twitter Card */}
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content={fullTitle} />
|
|
<meta name="twitter:description" content={fullDescription} />
|
|
{ogImages[0] && (
|
|
<meta name="twitter:image" content={ogImages[0]} />
|
|
)}
|
|
|
|
{/* Site Info */}
|
|
<meta name="author" content="KLZ Kabelwerke" />
|
|
<meta name="copyright" content="KLZ Kabelwerke" />
|
|
|
|
{/* Favicon (placeholder) */}
|
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function generateSEOMetadata(props: SEOProps) {
|
|
const site = getSiteInfo();
|
|
const fullTitle = props.title === 'Home' ? site.title : `${props.title} | ${site.title}`;
|
|
const description = props.description || site.description;
|
|
const canonicalUrl = `${site.baseUrl}${props.path || '/'}`;
|
|
|
|
const alternateLocale = props.locale === 'en' ? 'de' : 'en';
|
|
const alternatePath = props.path && props.path !== '/' ? props.path : '';
|
|
const alternateUrl = `${site.baseUrl}/${alternateLocale}${alternatePath}`;
|
|
|
|
return {
|
|
title: fullTitle,
|
|
description,
|
|
metadataBase: new URL(site.baseUrl),
|
|
alternates: {
|
|
canonical: canonicalUrl,
|
|
languages: {
|
|
[props.locale || 'en']: canonicalUrl,
|
|
[alternateLocale]: alternateUrl,
|
|
},
|
|
},
|
|
openGraph: {
|
|
title: fullTitle,
|
|
description,
|
|
type: props.type || 'website',
|
|
locale: props.locale || 'en',
|
|
siteName: site.title,
|
|
url: canonicalUrl,
|
|
...(props.images && props.images.length > 0 && {
|
|
images: props.images.map(img => ({ url: img, alt: fullTitle })),
|
|
}),
|
|
...(props.publishedTime && { publishedTime: props.publishedTime }),
|
|
...(props.modifiedTime && { modifiedTime: props.modifiedTime }),
|
|
...(props.authors && { authors: props.authors }),
|
|
},
|
|
twitter: {
|
|
card: 'summary_large_image',
|
|
title: fullTitle,
|
|
description,
|
|
...(props.images && props.images[0] && { images: [props.images[0]] }),
|
|
},
|
|
authors: props.authors ? props.authors.map(name => ({ name })) : undefined,
|
|
};
|
|
} |