initial migration
This commit is contained in:
130
lib/seo.ts
Normal file
130
lib/seo.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Metadata } from 'next';
|
||||
import { getSiteInfo } from './i18n';
|
||||
import type { Locale } from './i18n';
|
||||
|
||||
export interface SEOParams {
|
||||
title?: string;
|
||||
description?: string;
|
||||
locale?: Locale;
|
||||
canonical?: string;
|
||||
ogType?: 'website' | 'article' | 'product';
|
||||
ogImages?: string[];
|
||||
publishedTime?: string;
|
||||
updatedTime?: string;
|
||||
author?: string;
|
||||
}
|
||||
|
||||
export function generateSEOMetadata({
|
||||
title,
|
||||
description,
|
||||
locale = 'en',
|
||||
canonical,
|
||||
ogType = 'website',
|
||||
ogImages = [],
|
||||
publishedTime,
|
||||
updatedTime,
|
||||
author,
|
||||
}: SEOParams): Metadata {
|
||||
const site = getSiteInfo(locale);
|
||||
|
||||
const pageTitle = title ? `${title} | ${site.title}` : site.title;
|
||||
const pageDescription = description || site.description;
|
||||
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || site.baseUrl;
|
||||
const path = canonical || '/';
|
||||
const fullUrl = `${baseUrl}${path}`;
|
||||
|
||||
// Generate alternate URLs for both locales
|
||||
const alternates = {
|
||||
canonical: fullUrl,
|
||||
languages: {
|
||||
'en': `${baseUrl}${path.replace('/de/', '/')}`,
|
||||
'de': `${baseUrl}${path.startsWith('/de') ? path : `/de${path}`}`,
|
||||
},
|
||||
};
|
||||
|
||||
const openGraph = {
|
||||
title: pageTitle,
|
||||
description: pageDescription,
|
||||
url: fullUrl,
|
||||
siteName: site.title,
|
||||
locale: locale === 'de' ? 'de_DE' : 'en_US',
|
||||
type: ogType,
|
||||
...(ogImages.length > 0 && { images: ogImages }),
|
||||
...(publishedTime && { publishedTime }),
|
||||
...(updatedTime && { updatedTime }),
|
||||
...(author && { authors: [author] }),
|
||||
};
|
||||
|
||||
const twitter = {
|
||||
card: 'summary_large_image',
|
||||
title: pageTitle,
|
||||
description: pageDescription,
|
||||
...(ogImages.length > 0 && { images: ogImages }),
|
||||
};
|
||||
|
||||
return {
|
||||
title: pageTitle,
|
||||
description: pageDescription,
|
||||
alternates,
|
||||
openGraph,
|
||||
twitter,
|
||||
authors: author ? [{ name: author }] : undefined,
|
||||
metadataBase: new URL(baseUrl),
|
||||
};
|
||||
}
|
||||
|
||||
// Helper for blog posts
|
||||
export function getPostSEO(post: any, locale: Locale): Metadata {
|
||||
return generateSEOMetadata({
|
||||
title: post.title,
|
||||
description: post.excerptHtml?.replace(/<[^>]*>/g, '') || '',
|
||||
canonical: post.path,
|
||||
locale: locale,
|
||||
ogType: 'article',
|
||||
ogImages: post.featuredImage ? [post.featuredImage] : [],
|
||||
publishedTime: post.datePublished,
|
||||
updatedTime: post.updatedAt,
|
||||
author: 'KLZ Cables Team',
|
||||
});
|
||||
}
|
||||
|
||||
// Helper for products
|
||||
export function getProductSEO(product: any, locale: Locale): Metadata {
|
||||
return generateSEOMetadata({
|
||||
title: product.name,
|
||||
description: product.shortDescriptionHtml?.replace(/<[^>]*>/g, '') || '',
|
||||
canonical: product.path,
|
||||
locale: locale,
|
||||
ogType: 'product',
|
||||
ogImages: product.images || [],
|
||||
});
|
||||
}
|
||||
|
||||
// Helper for categories
|
||||
export function getCategorySEO(category: any, locale: Locale): Metadata {
|
||||
return generateSEOMetadata({
|
||||
title: category.name,
|
||||
description: category.description || `Products in ${category.name}`,
|
||||
canonical: category.path,
|
||||
locale: locale,
|
||||
ogType: 'website',
|
||||
});
|
||||
}
|
||||
|
||||
export function generateSitemapItem({
|
||||
path,
|
||||
lastmod,
|
||||
priority = 0.7,
|
||||
}: {
|
||||
path: string;
|
||||
lastmod?: string;
|
||||
priority?: number;
|
||||
}) {
|
||||
return {
|
||||
url: path,
|
||||
lastmod: lastmod || new Date().toISOString().split('T')[0],
|
||||
changefreq: 'weekly',
|
||||
priority,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user