feat: comprehensive SEO system with keywords and schema markup

- Add 4-locale keyword configurations (SR, EN, DE, FR)
- Create schema generators (Product, Organization, Breadcrumb)
- Add React components for JSON-LD rendering
- Implement caching for keyword performance
- Abstract all SEO logic for maintainability
This commit is contained in:
Unchained
2026-03-30 11:22:44 +02:00
parent 767afac606
commit 234b1f1739
17 changed files with 1957 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
import { ProductSchema } from './types';
import { Locale } from '../keywords/types';
import { getBrandKeywords, getCategoryKeywords } from '../keywords';
interface ProductData {
name: string;
slug: string;
description: string;
images: string[];
price: {
amount: number;
currency: string;
};
sku?: string;
availability?: 'InStock' | 'OutOfStock' | 'PreOrder';
category?: string;
rating?: {
value: number;
count: number;
};
}
/**
* Generate Product schema (JSON-LD)
* Pure function - takes product data, returns schema object
*
* @param baseUrl - Site base URL
* @param locale - Locale code
* @param product - Product data
* @returns ProductSchema object
*/
export function generateProductSchema(
baseUrl: string,
locale: Locale,
product: ProductData
): ProductSchema {
const brandKeywords = getBrandKeywords(locale);
const productUrl = `${baseUrl}/${locale === 'sr' ? '' : locale + '/'}products/${product.slug}`;
// Build full image URLs
const imageUrls = product.images.map(img =>
img.startsWith('http') ? img : `${baseUrl}${img}`
);
const schema: ProductSchema = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: imageUrls,
description: product.description.slice(0, 5000), // Schema.org limit
sku: product.sku,
brand: {
'@type': 'Brand',
name: brandKeywords.companyName,
},
offers: {
'@type': 'Offer',
url: productUrl,
price: product.price.amount.toString(),
priceCurrency: product.price.currency,
availability: `https://schema.org/${product.availability || 'InStock'}`,
itemCondition: 'https://schema.org/NewCondition',
},
};
// Add aggregate rating if available
if (product.rating && product.rating.count > 0) {
schema.aggregateRating = {
'@type': 'AggregateRating',
ratingValue: product.rating.value.toString(),
reviewCount: product.rating.count.toString(),
};
}
return schema;
}
/**
* Generate Product schema with category context
* Uses category-specific keywords for enhanced SEO
*
* @param baseUrl - Site base URL
* @param locale - Locale code
* @param product - Product data
* @param categoryKey - Category key for keyword targeting
* @returns ProductSchema object
*/
export function generateCategorizedProductSchema(
baseUrl: string,
locale: Locale,
product: ProductData,
categoryKey: 'antiAging' | 'hydration' | 'glow' | 'sensitive' | 'natural' | 'organic'
): ProductSchema {
const categoryKeywords = getCategoryKeywords(locale, categoryKey);
// Enhance description with category keywords
const enhancedDescription = product.description +
' ' + categoryKeywords.slice(0, 3).join('. ');
return generateProductSchema(baseUrl, locale, {
...product,
description: enhancedDescription,
});
}