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:
39
src/components/seo/JsonLd.tsx
Normal file
39
src/components/seo/JsonLd.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import Script from 'next/script';
|
||||||
|
import { SchemaType } from '@/lib/seo/schema/types';
|
||||||
|
|
||||||
|
interface JsonLdProps {
|
||||||
|
data: SchemaType | SchemaType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React component to render JSON-LD schema markup
|
||||||
|
* Uses Next.js Script component for proper loading
|
||||||
|
*
|
||||||
|
* @param data - Single schema object or array of schemas
|
||||||
|
* @returns Script component with JSON-LD
|
||||||
|
* @example
|
||||||
|
* <JsonLd data={productSchema} />
|
||||||
|
* <JsonLd data={[productSchema, breadcrumbSchema]} />
|
||||||
|
*/
|
||||||
|
export function JsonLd({ data }: JsonLdProps) {
|
||||||
|
// Handle single schema or array
|
||||||
|
const schemas = Array.isArray(data) ? data : [data];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{schemas.map((schema, index) => (
|
||||||
|
<Script
|
||||||
|
key={index}
|
||||||
|
id={`json-ld-${index}`}
|
||||||
|
type="application/ld+json"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(schema),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JsonLd;
|
||||||
41
src/components/seo/OrganizationSchema.tsx
Normal file
41
src/components/seo/OrganizationSchema.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { JsonLd } from './JsonLd';
|
||||||
|
import { generateOrganizationSchema, generateWebSiteSchema } from '@/lib/seo/schema/organizationSchema';
|
||||||
|
import { Locale } from '@/lib/seo/keywords/types';
|
||||||
|
|
||||||
|
interface OrganizationSchemaProps {
|
||||||
|
baseUrl: string;
|
||||||
|
locale: Locale;
|
||||||
|
logoUrl: string;
|
||||||
|
socialProfiles?: string[];
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization schema component
|
||||||
|
* Renders Organization + WebSite JSON-LD schemas
|
||||||
|
*
|
||||||
|
* @param baseUrl - Site base URL
|
||||||
|
* @param locale - Current locale
|
||||||
|
* @param logoUrl - URL to organization logo
|
||||||
|
* @param socialProfiles - Array of social media profile URLs
|
||||||
|
* @param email - Contact email
|
||||||
|
*/
|
||||||
|
export function OrganizationSchema({
|
||||||
|
baseUrl,
|
||||||
|
locale,
|
||||||
|
logoUrl,
|
||||||
|
socialProfiles,
|
||||||
|
email,
|
||||||
|
}: OrganizationSchemaProps) {
|
||||||
|
const orgSchema = generateOrganizationSchema(baseUrl, locale, {
|
||||||
|
logoUrl,
|
||||||
|
socialProfiles,
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
|
||||||
|
const websiteSchema = generateWebSiteSchema(baseUrl, locale);
|
||||||
|
|
||||||
|
return <JsonLd data={[orgSchema, websiteSchema]} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrganizationSchema;
|
||||||
67
src/components/seo/ProductSchema.tsx
Normal file
67
src/components/seo/ProductSchema.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { JsonLd } from './JsonLd';
|
||||||
|
import { generateProductSchema, generateCategorizedProductSchema } from '@/lib/seo/schema/productSchema';
|
||||||
|
import { generateProductBreadcrumbs } from '@/lib/seo/schema/breadcrumbSchema';
|
||||||
|
import { Locale } from '@/lib/seo/keywords/types';
|
||||||
|
|
||||||
|
interface ProductSchemaProps {
|
||||||
|
baseUrl: string;
|
||||||
|
locale: Locale;
|
||||||
|
product: {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description: string;
|
||||||
|
images: string[];
|
||||||
|
price: {
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
};
|
||||||
|
sku?: string;
|
||||||
|
availability?: 'InStock' | 'OutOfStock' | 'PreOrder';
|
||||||
|
};
|
||||||
|
category?: 'antiAging' | 'hydration' | 'glow' | 'sensitive' | 'natural' | 'organic';
|
||||||
|
rating?: {
|
||||||
|
value: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
includeBreadcrumbs?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product schema component
|
||||||
|
* Renders Product + BreadcrumbList JSON-LD schemas
|
||||||
|
*
|
||||||
|
* @param baseUrl - Site base URL
|
||||||
|
* @param locale - Current locale
|
||||||
|
* @param product - Product data object
|
||||||
|
* @param category - Optional category for enhanced targeting
|
||||||
|
* @param rating - Optional aggregate rating data
|
||||||
|
* @param includeBreadcrumbs - Whether to include breadcrumb schema (default: true)
|
||||||
|
*/
|
||||||
|
export function ProductSchema({
|
||||||
|
baseUrl,
|
||||||
|
locale,
|
||||||
|
product,
|
||||||
|
category,
|
||||||
|
rating,
|
||||||
|
includeBreadcrumbs = true,
|
||||||
|
}: ProductSchemaProps) {
|
||||||
|
// Generate product schema
|
||||||
|
const productSchema = category
|
||||||
|
? generateCategorizedProductSchema(baseUrl, locale, { ...product, rating }, category)
|
||||||
|
: generateProductSchema(baseUrl, locale, { ...product, rating });
|
||||||
|
|
||||||
|
// Generate breadcrumbs if requested
|
||||||
|
if (includeBreadcrumbs) {
|
||||||
|
const breadcrumbSchema = generateProductBreadcrumbs(
|
||||||
|
baseUrl,
|
||||||
|
locale,
|
||||||
|
product.name,
|
||||||
|
product.slug
|
||||||
|
);
|
||||||
|
return <JsonLd data={[productSchema, breadcrumbSchema]} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <JsonLd data={productSchema} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductSchema;
|
||||||
9
src/components/seo/index.ts
Normal file
9
src/components/seo/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* SEO React Components
|
||||||
|
* Structured data and metadata components
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Schema components
|
||||||
|
export { JsonLd } from './JsonLd';
|
||||||
|
export { OrganizationSchema } from './OrganizationSchema';
|
||||||
|
export { ProductSchema } from './ProductSchema';
|
||||||
58
src/lib/seo/keywords/config/keywordStrategy.ts
Normal file
58
src/lib/seo/keywords/config/keywordStrategy.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { Locale, LocaleKeywords } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyword Strategy Configuration
|
||||||
|
* Defines how keywords should be used across the site
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const keywordStrategy = {
|
||||||
|
density: {
|
||||||
|
min: 0.5, // 0.5% minimum keyword density
|
||||||
|
max: 2.5, // 2.5% maximum (avoid keyword stuffing)
|
||||||
|
optimal: 1.5 // 1.5% optimal density
|
||||||
|
},
|
||||||
|
|
||||||
|
placement: {
|
||||||
|
title: true, // Include keyword in page title
|
||||||
|
h1: true, // Include keyword in H1
|
||||||
|
h2: true, // Include in at least one H2
|
||||||
|
firstParagraph: true, // Include in first 100 words
|
||||||
|
metaDescription: true, // Include in meta description
|
||||||
|
altText: true // Include in image alt text where relevant
|
||||||
|
},
|
||||||
|
|
||||||
|
variations: true, // Use keyword variations/synonyms
|
||||||
|
|
||||||
|
// Meta title/descriptions character limits
|
||||||
|
metaLimits: {
|
||||||
|
titleMin: 30,
|
||||||
|
titleMax: 60,
|
||||||
|
descriptionMin: 120,
|
||||||
|
descriptionMax: 160
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get keyword usage recommendations for a page
|
||||||
|
*/
|
||||||
|
export function getKeywordRecommendations(
|
||||||
|
pageType: keyof LocaleKeywords['pages'],
|
||||||
|
locale: Locale
|
||||||
|
): { primary: string[]; secondary: string[]; recommendations: string[] } {
|
||||||
|
const recommendations: string[] = [
|
||||||
|
`Use primary keywords within first 100 words`,
|
||||||
|
`Include at least one primary keyword in H1`,
|
||||||
|
`Meta title should be ${keywordStrategy.metaLimits.titleMin}-${keywordStrategy.metaLimits.titleMax} characters`,
|
||||||
|
`Meta description should be ${keywordStrategy.metaLimits.descriptionMin}-${keywordStrategy.metaLimits.descriptionMax} characters`,
|
||||||
|
`Maintain ${keywordStrategy.density.optimal}% keyword density`,
|
||||||
|
`Use keyword variations naturally throughout content`
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: [], // Will be populated by getKeywords
|
||||||
|
secondary: [], // Will be populated by getKeywords
|
||||||
|
recommendations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default keywordStrategy;
|
||||||
46
src/lib/seo/keywords/index.ts
Normal file
46
src/lib/seo/keywords/index.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* SEO Keywords Module
|
||||||
|
* Centralized, localized keyword management for SEO optimization
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* import { getKeywords, getPageKeywords, Locale } from '@/lib/seo/keywords';
|
||||||
|
*
|
||||||
|
* const keywords = getKeywords('sr');
|
||||||
|
* const homeKeywords = getPageKeywords('sr', 'home');
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Types
|
||||||
|
export type {
|
||||||
|
Locale,
|
||||||
|
LocaleKeywords,
|
||||||
|
BrandKeywords,
|
||||||
|
PageKeywords,
|
||||||
|
ProductCategoryKeywords,
|
||||||
|
ContentKeywords,
|
||||||
|
CompetitorKeywords,
|
||||||
|
KeywordStrategy
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
// Main functions
|
||||||
|
export {
|
||||||
|
getKeywords,
|
||||||
|
getPageKeywords,
|
||||||
|
getCategoryKeywords,
|
||||||
|
getContentKeywords,
|
||||||
|
getCompetitorKeywords,
|
||||||
|
getBrandKeywords,
|
||||||
|
clearKeywordsCache,
|
||||||
|
getAvailableLocales,
|
||||||
|
isValidLocale
|
||||||
|
} from './utils/getKeywords';
|
||||||
|
|
||||||
|
// Keyword strategy
|
||||||
|
export { keywordStrategy, getKeywordRecommendations } from './config/keywordStrategy';
|
||||||
|
|
||||||
|
// Locale-specific exports (for direct access if needed)
|
||||||
|
export { serbianKeywords } from './locales/sr';
|
||||||
|
export { englishKeywords } from './locales/en';
|
||||||
|
export { germanKeywords } from './locales/de';
|
||||||
|
export { frenchKeywords } from './locales/fr';
|
||||||
|
|
||||||
|
|
||||||
274
src/lib/seo/keywords/locales/de.ts
Normal file
274
src/lib/seo/keywords/locales/de.ts
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
import { LocaleKeywords } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* German (DE) SEO Keywords Configuration
|
||||||
|
* Primary market: Germany, Austria, Switzerland (DACH)
|
||||||
|
* Language: German
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const germanKeywords: LocaleKeywords = {
|
||||||
|
locale: 'de',
|
||||||
|
|
||||||
|
brand: {
|
||||||
|
companyName: 'ManoonOils',
|
||||||
|
tagline: 'Premium Natürliche Anti-Aging Seren und Öle Für Gesicht, Haut & Haar',
|
||||||
|
category: 'Naturkosmetik',
|
||||||
|
valueProposition: 'handgefertigte Produkte aus natürlichen Inhaltsstoffen ohne Chemikalien'
|
||||||
|
},
|
||||||
|
|
||||||
|
pages: {
|
||||||
|
home: {
|
||||||
|
primary: [
|
||||||
|
'natürliches Gesichtsserum',
|
||||||
|
'Bio Hautpflege',
|
||||||
|
'Anti-Aging Serum natürlich'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'natürliche Öle für das Gesicht',
|
||||||
|
'Clean Beauty Produkte',
|
||||||
|
'Serum ohne Chemikalien',
|
||||||
|
'natürliche Hautpflege'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'bestes natürliches Serum für reife Haut',
|
||||||
|
'wo kann man Bio Hautpflege online kaufen',
|
||||||
|
'natürliche Anti-Aging Produkte für das Gesicht',
|
||||||
|
'Gesichtsserum mit natürlichen Inhaltsstoffen',
|
||||||
|
'handgemachte Naturkosmetik'
|
||||||
|
],
|
||||||
|
metaTitle: 'ManoonOils | Natürliches Gesichtsserum | Bio Hautpflege',
|
||||||
|
metaDescription: 'Entdecken Sie unsere Kollektion von Premium natürlichen Gesichtsseren. Anti-Aging, Feuchtigkeit und strahlende Haut ohne Chemikalien. Handgefertigte Produkte.'
|
||||||
|
},
|
||||||
|
|
||||||
|
products: {
|
||||||
|
primary: [
|
||||||
|
'natürliches Gesichtsserum kaufen',
|
||||||
|
'Bio Gesichtspflege Produkte',
|
||||||
|
'Anti-Aging Serum natürlich'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'Falten Serum',
|
||||||
|
'Glow Serum',
|
||||||
|
'natürliche Gesichtsöle',
|
||||||
|
'Serum ohne Parabene'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'natürliches Serum für trockene Gesichtshaut',
|
||||||
|
'Bio Anti-Aging Serum Preis',
|
||||||
|
'Vitamin C Serum für das Gesicht',
|
||||||
|
'natürliches Serum für empfindliche Haut',
|
||||||
|
'wo kann man natürliches Serum kaufen'
|
||||||
|
],
|
||||||
|
metaTitle: 'Natürliches Gesichtsserum | Bio Hautpflege | ManoonOils',
|
||||||
|
metaDescription: 'Durchsuchen Sie unsere Kollektion von Premium natürlichen Gesichtsseren. Anti-Aging, Feuchtigkeit und strahlende Haut ohne Chemikalien.'
|
||||||
|
},
|
||||||
|
|
||||||
|
product: {
|
||||||
|
primary: [
|
||||||
|
'{{productName}} Serum',
|
||||||
|
'natürliches Gesichtsserum',
|
||||||
|
'Bio Hautpflege'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'Anti-Falten Serum',
|
||||||
|
'Anti-Aging Serum',
|
||||||
|
'natürliche Gesichtspflege',
|
||||||
|
'Serum ohne Chemikalien'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'{{productName}} Bewertungen',
|
||||||
|
'{{productName}} Preis',
|
||||||
|
'{{productName}} wo kaufen',
|
||||||
|
'{{productName}} Ergebnisse',
|
||||||
|
'bestes Serum für {{concern}}'
|
||||||
|
],
|
||||||
|
metaTitle: '{{productName}} | Natürliches Gesichtsserum | ManoonOils',
|
||||||
|
metaDescription: '{{productName}} - Premium natürliches Serum für {{concern}}. {{benefits}}. Ohne Chemikalien, handgefertigt.'
|
||||||
|
},
|
||||||
|
|
||||||
|
about: {
|
||||||
|
primary: [
|
||||||
|
'über manoonoils',
|
||||||
|
'Naturkosmetik Marke',
|
||||||
|
'handgemachte Hautpflege Hersteller'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'unsere Geschichte',
|
||||||
|
'Mission und Vision',
|
||||||
|
'natürliche Inhaltsstoffe',
|
||||||
|
'handgefertigte Produkte'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'wer steckt hinter manoonoils',
|
||||||
|
'warum Naturkosmetik wählen',
|
||||||
|
'wie unsere Produkte hergestellt werden',
|
||||||
|
'ethische Beauty Produktion'
|
||||||
|
],
|
||||||
|
metaTitle: 'Über uns | ManoonOils | Naturkosmetik',
|
||||||
|
metaDescription: 'Lernen Sie ManoonOils kennen - einen Hersteller von Premium natürlichen Seren. Unsere Geschichte, Mission und Engagement für Qualität ohne Kompromisse.'
|
||||||
|
},
|
||||||
|
|
||||||
|
contact: {
|
||||||
|
primary: [
|
||||||
|
'kontakt manoonoils',
|
||||||
|
'natürliches Serum kaufen',
|
||||||
|
'Hautpflege Zusammenarbeit'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'Naturkosmetik Verkauf',
|
||||||
|
'Großhandel Serum',
|
||||||
|
'Distributoren'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'wie bestellt man bei manoonoils',
|
||||||
|
'manoonoils Kontakt Telefon',
|
||||||
|
'wo kann man Produkte kaufen',
|
||||||
|
'Zusammenarbeit mit manoonoils'
|
||||||
|
],
|
||||||
|
metaTitle: 'Kontakt | ManoonOils | Natürliches Serum kaufen',
|
||||||
|
metaDescription: 'Kontaktieren Sie uns für Bestellungen, Produktfragen oder Geschäftszusammenarbeit. ManoonOils - Naturkosmetik.'
|
||||||
|
},
|
||||||
|
|
||||||
|
checkout: {
|
||||||
|
primary: [],
|
||||||
|
secondary: [],
|
||||||
|
longTail: [],
|
||||||
|
metaTitle: 'Kauf abschließen | ManoonOils',
|
||||||
|
metaDescription: 'Schließen Sie Ihren Kauf von natürlichen Seren sicher ab. Schneller Versand nach Deutschland und Österreich.'
|
||||||
|
},
|
||||||
|
|
||||||
|
blog: {
|
||||||
|
primary: [
|
||||||
|
'Hautpflege Tipps',
|
||||||
|
'natürliche Hautpflege',
|
||||||
|
'Anti-Aging Tipps'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'wie benutzt man Serum',
|
||||||
|
'Hautpflege Routine',
|
||||||
|
'natürliche Inhaltsstoffe',
|
||||||
|
'Pflege für reife Haut'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'welche Öle sind am besten für das Gesicht',
|
||||||
|
'wie reduziert man Falten natürlich',
|
||||||
|
'tägliche Hautpflege Routine',
|
||||||
|
'natürliche Alternative zu Retinol'
|
||||||
|
],
|
||||||
|
metaTitle: 'Blog | Hautpflege Tipps | ManoonOils',
|
||||||
|
metaDescription: 'Expertentipps für die Gesichtspflege, natürliche Alternativen und Anleitungen für gesunde, strahlende Haut. Lesen Sie unseren Blog.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
categories: {
|
||||||
|
antiAging: [
|
||||||
|
'Anti-Aging Serum',
|
||||||
|
'Falten Serum',
|
||||||
|
'Anti-Aging Hautpflege',
|
||||||
|
'natürliches Anti-Aging',
|
||||||
|
'Serum für reife Haut',
|
||||||
|
'Anti-Aging Kosmetik'
|
||||||
|
],
|
||||||
|
hydration: [
|
||||||
|
'feuchtigkeitsspendendes Serum',
|
||||||
|
'Serum für trockene Haut',
|
||||||
|
'Feuchtigkeit für das Gesicht',
|
||||||
|
'Gesichtsfeuchtigkeit',
|
||||||
|
'Serum für dehydrierte Haut'
|
||||||
|
],
|
||||||
|
glow: [
|
||||||
|
'Glow Serum',
|
||||||
|
'Strahlendes Serum',
|
||||||
|
'strahlende Haut',
|
||||||
|
'Serum für Leuchtkraft',
|
||||||
|
'gesunder Glow'
|
||||||
|
],
|
||||||
|
sensitive: [
|
||||||
|
'Serum für empfindliche Haut',
|
||||||
|
'sanfte Gesichtspflege',
|
||||||
|
'duftfreies Serum',
|
||||||
|
'hypoallergene Hautpflege',
|
||||||
|
'Serum für Rosacea'
|
||||||
|
],
|
||||||
|
natural: [
|
||||||
|
'natürliches Serum',
|
||||||
|
'Kräuterserum',
|
||||||
|
'Serum aus natürlichen Inhaltsstoffen',
|
||||||
|
'Naturkosmetik',
|
||||||
|
'selbstgemachtes Serum'
|
||||||
|
],
|
||||||
|
organic: [
|
||||||
|
'Bio Serum',
|
||||||
|
'Öko Serum',
|
||||||
|
'Biokosmetik',
|
||||||
|
'zertifiziert Bio',
|
||||||
|
'Öko Serum'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
educational: [
|
||||||
|
'wie benutzt man Gesichtsserum',
|
||||||
|
'was ist der Unterschied zwischen Serum und Creme',
|
||||||
|
'wie erkennt man Qualitäts-Naturkosmetik',
|
||||||
|
'Reihenfolge beim Auftragen von Hautpflegeprodukten',
|
||||||
|
'wie liest man kosmetische Produktetiketten'
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
'Vorteile von natürlichen Seren',
|
||||||
|
'warum Bio Kosmetik wählen',
|
||||||
|
'Vorteile von Arganöl für die Haut',
|
||||||
|
'Hagebuttenöl für Falten',
|
||||||
|
'Squalan - alles was Sie wissen müssen'
|
||||||
|
],
|
||||||
|
comparison: [
|
||||||
|
'natürlich vs synthetische Kosmetik',
|
||||||
|
'Serum oder Creme - was ist besser',
|
||||||
|
'Retinol vs Bakuchiol',
|
||||||
|
'chemisches Peeling vs enzymatisches',
|
||||||
|
'Haut vor und nach natürlichen Seren'
|
||||||
|
],
|
||||||
|
ingredients: [
|
||||||
|
'Arganöl Eigenschaften',
|
||||||
|
'Jojobaöl für das Gesicht',
|
||||||
|
'Vitamin C in Kosmetik',
|
||||||
|
'natürliche Hyaluronsäure',
|
||||||
|
'Öko Zertifizierungen Kosmetik'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
competitors: {
|
||||||
|
brands: [
|
||||||
|
'The Ordinary',
|
||||||
|
'Paula\'s Choice',
|
||||||
|
'La Roche Posay',
|
||||||
|
'Vichy',
|
||||||
|
'L\'Oreal',
|
||||||
|
'Garnier',
|
||||||
|
'Nuxe',
|
||||||
|
'Caudalie',
|
||||||
|
'Drunk Elephant',
|
||||||
|
'SkinCeuticals',
|
||||||
|
'Sunday Riley',
|
||||||
|
'Tata Harper',
|
||||||
|
'Weleda',
|
||||||
|
'Sante',
|
||||||
|
'Logona'
|
||||||
|
],
|
||||||
|
comparisons: [
|
||||||
|
'manoonoils vs the ordinary',
|
||||||
|
'natürliches Serum vs Drogerie',
|
||||||
|
'handgemachte Kosmetik vs kommerziell',
|
||||||
|
'Serum ohne Chemikalien vs Standard'
|
||||||
|
],
|
||||||
|
alternatives: [
|
||||||
|
'Alternative zu The Ordinary',
|
||||||
|
'natürliche Alternative zu Retinol',
|
||||||
|
'günstige Alternative zu SkinCeuticals',
|
||||||
|
'handgemachtes Produkt statt Import',
|
||||||
|
'Serum ohne Silikone Alternative'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default germanKeywords;
|
||||||
271
src/lib/seo/keywords/locales/en.ts
Normal file
271
src/lib/seo/keywords/locales/en.ts
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
import { LocaleKeywords } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* English (EN) SEO Keywords Configuration
|
||||||
|
* Primary market: International/US/UK
|
||||||
|
* Language: English
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const englishKeywords: LocaleKeywords = {
|
||||||
|
locale: 'en',
|
||||||
|
|
||||||
|
brand: {
|
||||||
|
companyName: 'ManoonOils',
|
||||||
|
tagline: 'Premium Natural Anti Age Serums and Oils For Face, Skin & Hair',
|
||||||
|
category: 'natural cosmetics',
|
||||||
|
valueProposition: 'handmade products from natural ingredients without chemicals'
|
||||||
|
},
|
||||||
|
|
||||||
|
pages: {
|
||||||
|
home: {
|
||||||
|
primary: [
|
||||||
|
'natural face serum',
|
||||||
|
'organic skincare',
|
||||||
|
'anti aging serum natural'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'natural oils for face',
|
||||||
|
'clean beauty products',
|
||||||
|
'serum without chemicals',
|
||||||
|
'natural skin care'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'best natural serum for mature skin',
|
||||||
|
'where to buy organic skincare online',
|
||||||
|
'natural anti aging products for face',
|
||||||
|
'face serum with natural ingredients',
|
||||||
|
'handmade natural cosmetics'
|
||||||
|
],
|
||||||
|
metaTitle: 'ManoonOils | Natural Face Serum | Organic Skincare',
|
||||||
|
metaDescription: 'Discover our collection of premium natural face serums. Anti-aging, hydration and radiant skin without chemicals. Handmade products.'
|
||||||
|
},
|
||||||
|
|
||||||
|
products: {
|
||||||
|
primary: [
|
||||||
|
'natural face serum shop',
|
||||||
|
'organic face care products',
|
||||||
|
'anti aging serum natural'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'wrinkle serum',
|
||||||
|
'glow serum',
|
||||||
|
'natural face oils',
|
||||||
|
'serum without parabens'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'natural serum for dry facial skin',
|
||||||
|
'organic anti aging serum price',
|
||||||
|
'vitamin C serum for face',
|
||||||
|
'natural serum for sensitive skin',
|
||||||
|
'where to buy natural serum'
|
||||||
|
],
|
||||||
|
metaTitle: 'Natural Face Serum | Organic Skincare | ManoonOils',
|
||||||
|
metaDescription: 'Browse our collection of premium natural face serums. Anti-aging, hydration and radiant skin without chemicals.'
|
||||||
|
},
|
||||||
|
|
||||||
|
product: {
|
||||||
|
primary: [
|
||||||
|
'{{productName}} serum',
|
||||||
|
'natural face serum',
|
||||||
|
'organic skincare'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'anti wrinkle serum',
|
||||||
|
'anti aging serum',
|
||||||
|
'natural face care',
|
||||||
|
'serum without chemicals'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'{{productName}} reviews',
|
||||||
|
'{{productName}} price',
|
||||||
|
'{{productName}} where to buy',
|
||||||
|
'{{productName}} results',
|
||||||
|
'best serum for {{concern}}'
|
||||||
|
],
|
||||||
|
metaTitle: '{{productName}} | Natural Face Serum | ManoonOils',
|
||||||
|
metaDescription: '{{productName}} - premium natural serum for {{concern}}. {{benefits}}. Without chemicals, handmade.'
|
||||||
|
},
|
||||||
|
|
||||||
|
about: {
|
||||||
|
primary: [
|
||||||
|
'about manoonoils',
|
||||||
|
'natural cosmetics brand',
|
||||||
|
'handmade skincare manufacturer'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'our story',
|
||||||
|
'mission and vision',
|
||||||
|
'natural ingredients',
|
||||||
|
'handcrafted products'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'who is behind manoonoils',
|
||||||
|
'why choose natural cosmetics',
|
||||||
|
'how our products are made',
|
||||||
|
'ethical beauty production'
|
||||||
|
],
|
||||||
|
metaTitle: 'About Us | ManoonOils | Natural Cosmetics',
|
||||||
|
metaDescription: 'Meet ManoonOils - a manufacturer of premium natural serums. Our story, mission and commitment to quality without compromise.'
|
||||||
|
},
|
||||||
|
|
||||||
|
contact: {
|
||||||
|
primary: [
|
||||||
|
'contact manoonoils',
|
||||||
|
'buy natural serum',
|
||||||
|
'skincare collaboration'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'natural cosmetics sales',
|
||||||
|
'wholesale serum',
|
||||||
|
'distributors'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'how to order manoonoils',
|
||||||
|
'manoonoils contact phone',
|
||||||
|
'where to buy products',
|
||||||
|
'collaboration with manoonoils'
|
||||||
|
],
|
||||||
|
metaTitle: 'Contact | ManoonOils | Buy Natural Serum',
|
||||||
|
metaDescription: 'Contact us for orders, product questions or business collaboration. ManoonOils - natural cosmetics.'
|
||||||
|
},
|
||||||
|
|
||||||
|
checkout: {
|
||||||
|
primary: [],
|
||||||
|
secondary: [],
|
||||||
|
longTail: [],
|
||||||
|
metaTitle: 'Complete Purchase | ManoonOils',
|
||||||
|
metaDescription: 'Securely complete your purchase of natural serums. Fast shipping worldwide.'
|
||||||
|
},
|
||||||
|
|
||||||
|
blog: {
|
||||||
|
primary: [
|
||||||
|
'skincare tips',
|
||||||
|
'natural skin care',
|
||||||
|
'anti aging tips'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'how to use serum',
|
||||||
|
'skincare routine',
|
||||||
|
'natural ingredients',
|
||||||
|
'mature skin care'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'which oils are best for face',
|
||||||
|
'how to reduce wrinkles naturally',
|
||||||
|
'daily skincare routine',
|
||||||
|
'natural alternative to retinol'
|
||||||
|
],
|
||||||
|
metaTitle: 'Blog | Skincare Tips | ManoonOils',
|
||||||
|
metaDescription: 'Expert tips for facial care, natural alternatives and guides for healthy, glowing skin. Read our blog.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
categories: {
|
||||||
|
antiAging: [
|
||||||
|
'anti aging serum',
|
||||||
|
'wrinkle serum',
|
||||||
|
'anti aging skincare',
|
||||||
|
'natural anti age',
|
||||||
|
'serum for mature skin',
|
||||||
|
'anti aging cosmetics'
|
||||||
|
],
|
||||||
|
hydration: [
|
||||||
|
'hydrating serum',
|
||||||
|
'serum for dry skin',
|
||||||
|
'moisture for face',
|
||||||
|
'face hydration',
|
||||||
|
'serum for dehydrated skin'
|
||||||
|
],
|
||||||
|
glow: [
|
||||||
|
'glow serum',
|
||||||
|
'radiance serum',
|
||||||
|
'glowing skin',
|
||||||
|
'serum for brightness',
|
||||||
|
'healthy glow'
|
||||||
|
],
|
||||||
|
sensitive: [
|
||||||
|
'serum for sensitive skin',
|
||||||
|
'gentle face care',
|
||||||
|
'fragrance free serum',
|
||||||
|
'hypoallergenic skincare',
|
||||||
|
'serum for rosacea'
|
||||||
|
],
|
||||||
|
natural: [
|
||||||
|
'natural serum',
|
||||||
|
'herbal serum',
|
||||||
|
'serum from natural ingredients',
|
||||||
|
'natural cosmetics',
|
||||||
|
'homemade serum'
|
||||||
|
],
|
||||||
|
organic: [
|
||||||
|
'organic serum',
|
||||||
|
'bio serum',
|
||||||
|
'organic cosmetics',
|
||||||
|
'certified organic',
|
||||||
|
'eco serum'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
educational: [
|
||||||
|
'how to use face serum',
|
||||||
|
'what is the difference between serum and cream',
|
||||||
|
'how to recognize quality natural cosmetics',
|
||||||
|
'order of applying skincare products',
|
||||||
|
'how to read cosmetic product labels'
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
'benefits of using natural serums',
|
||||||
|
'why choose organic cosmetics',
|
||||||
|
'benefits of argan oil for skin',
|
||||||
|
'rosehip oil for wrinkles',
|
||||||
|
'squalane - everything you need to know'
|
||||||
|
],
|
||||||
|
comparison: [
|
||||||
|
'natural vs synthetic cosmetics',
|
||||||
|
'serum or cream - which is better',
|
||||||
|
'retinol vs bakuchiol',
|
||||||
|
'chemical peel vs enzymatic',
|
||||||
|
'skin before and after natural serums'
|
||||||
|
],
|
||||||
|
ingredients: [
|
||||||
|
'argan oil properties',
|
||||||
|
'jojoba oil for face',
|
||||||
|
'vitamin C in cosmetics',
|
||||||
|
'natural hyaluronic acid',
|
||||||
|
'eco certifications cosmetics'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
competitors: {
|
||||||
|
brands: [
|
||||||
|
'The Ordinary',
|
||||||
|
'Paula\'s Choice',
|
||||||
|
'La Roche Posay',
|
||||||
|
'Vichy',
|
||||||
|
'L\'Oreal',
|
||||||
|
'Garnier',
|
||||||
|
'Nuxe',
|
||||||
|
'Caudalie',
|
||||||
|
'Drunk Elephant',
|
||||||
|
'SkinCeuticals',
|
||||||
|
'Sunday Riley',
|
||||||
|
'Tata Harper'
|
||||||
|
],
|
||||||
|
comparisons: [
|
||||||
|
'manoonoils vs the ordinary',
|
||||||
|
'natural serum vs drugstore',
|
||||||
|
'handmade cosmetics vs commercial',
|
||||||
|
'serum without chemicals vs standard'
|
||||||
|
],
|
||||||
|
alternatives: [
|
||||||
|
'alternative to the ordinary',
|
||||||
|
'natural alternative to retinol',
|
||||||
|
'affordable alternative to skinceuticals',
|
||||||
|
'handmade product instead of imported',
|
||||||
|
'serum without silicone alternative'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default englishKeywords;
|
||||||
275
src/lib/seo/keywords/locales/fr.ts
Normal file
275
src/lib/seo/keywords/locales/fr.ts
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
import { LocaleKeywords } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* French (FR) SEO Keywords Configuration
|
||||||
|
* Primary market: France, Belgium, Switzerland, Canada
|
||||||
|
* Language: French
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const frenchKeywords: LocaleKeywords = {
|
||||||
|
locale: 'fr',
|
||||||
|
|
||||||
|
brand: {
|
||||||
|
companyName: 'ManoonOils',
|
||||||
|
tagline: 'Sérums et Huiles Anti-Âge Naturels Premium Pour Visage, Peau & Cheveux',
|
||||||
|
category: 'cosmétiques naturels',
|
||||||
|
valueProposition: 'produits artisanaux aux ingrédients naturels sans produits chimiques'
|
||||||
|
},
|
||||||
|
|
||||||
|
pages: {
|
||||||
|
home: {
|
||||||
|
primary: [
|
||||||
|
'sérum visage naturel',
|
||||||
|
'cosmétique bio',
|
||||||
|
'sérum anti-âge naturel'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'huiles naturelles pour le visage',
|
||||||
|
'produits clean beauty',
|
||||||
|
'sérum sans produits chimiques',
|
||||||
|
'soin naturel de la peau'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'meilleur sérum naturel pour peau mature',
|
||||||
|
'où acheter cosmétique bio en ligne',
|
||||||
|
'produits anti-âge naturels pour le visage',
|
||||||
|
'sérum visage aux ingrédients naturels',
|
||||||
|
'cosmétique artisanale naturelle'
|
||||||
|
],
|
||||||
|
metaTitle: 'ManoonOils | Sérum Visage Naturel | Cosmétique Bio',
|
||||||
|
metaDescription: 'Découvrez notre collection de sérums visage naturels premium. Anti-âge, hydratation et peau rayonnante sans produits chimiques. Produits artisanaux.'
|
||||||
|
},
|
||||||
|
|
||||||
|
products: {
|
||||||
|
primary: [
|
||||||
|
'acheter sérum visage naturel',
|
||||||
|
'produits soin visage bio',
|
||||||
|
'sérum anti-âge naturel'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'sérum anti-rides',
|
||||||
|
'sérum éclat',
|
||||||
|
'huiles naturelles visage',
|
||||||
|
'sérum sans parabènes'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'sérum naturel pour peau sèche visage',
|
||||||
|
'prix sérum anti-âge bio',
|
||||||
|
'sérum vitamine C visage',
|
||||||
|
'sérum naturel pour peau sensible',
|
||||||
|
'où acheter sérum naturel'
|
||||||
|
],
|
||||||
|
metaTitle: 'Sérum Visage Naturel | Cosmétique Bio | ManoonOils',
|
||||||
|
metaDescription: 'Parcourez notre collection de sérums visage naturels premium. Anti-âge, hydratation et peau rayonnante sans produits chimiques.'
|
||||||
|
},
|
||||||
|
|
||||||
|
product: {
|
||||||
|
primary: [
|
||||||
|
'sérum {{productName}}',
|
||||||
|
'sérum visage naturel',
|
||||||
|
'cosmétique bio'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'sérum anti-rides',
|
||||||
|
'sérum anti-âge',
|
||||||
|
'soin naturel visage',
|
||||||
|
'sérum sans produits chimiques'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'{{productName}} avis',
|
||||||
|
'{{productName}} prix',
|
||||||
|
'{{productName}} où acheter',
|
||||||
|
'{{productName}} résultats',
|
||||||
|
'meilleur sérum pour {{concern}}'
|
||||||
|
],
|
||||||
|
metaTitle: '{{productName}} | Sérum Visage Naturel | ManoonOils',
|
||||||
|
metaDescription: '{{productName}} - sérum naturel premium pour {{concern}}. {{benefits}}. Sans produits chimiques, artisanal.'
|
||||||
|
},
|
||||||
|
|
||||||
|
about: {
|
||||||
|
primary: [
|
||||||
|
'à propos manoonoils',
|
||||||
|
'marque cosmétiques naturels',
|
||||||
|
'fabricant soin artisanal'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'notre histoire',
|
||||||
|
'mission et vision',
|
||||||
|
'ingrédients naturels',
|
||||||
|
'produits artisanaux'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'qui est derrière manoonoils',
|
||||||
|
'pourquoi choisir cosmétique naturel',
|
||||||
|
'comment nos produits sont fabriqués',
|
||||||
|
'production beauté éthique'
|
||||||
|
],
|
||||||
|
metaTitle: 'À propos | ManoonOils | Cosmétiques Naturels',
|
||||||
|
metaDescription: 'Découvrez ManoonOils - un fabricant de sérums naturels premium. Notre histoire, mission et engagement pour la qualité sans compromis.'
|
||||||
|
},
|
||||||
|
|
||||||
|
contact: {
|
||||||
|
primary: [
|
||||||
|
'contact manoonoils',
|
||||||
|
'acheter sérum naturel',
|
||||||
|
'collaboration cosmétique'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'vente cosmétiques naturels',
|
||||||
|
'sérum en gros',
|
||||||
|
'distributeurs'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'comment commander chez manoonoils',
|
||||||
|
'téléphone contact manoonoils',
|
||||||
|
'où acheter les produits',
|
||||||
|
'collaboration avec manoonoils'
|
||||||
|
],
|
||||||
|
metaTitle: 'Contact | ManoonOils | Acheter Sérum Naturel',
|
||||||
|
metaDescription: 'Contactez-nous pour commandes, questions produits ou collaboration commerciale. ManoonOils - cosmétiques naturels.'
|
||||||
|
},
|
||||||
|
|
||||||
|
checkout: {
|
||||||
|
primary: [],
|
||||||
|
secondary: [],
|
||||||
|
longTail: [],
|
||||||
|
metaTitle: 'Finaliser Achat | ManoonOils',
|
||||||
|
metaDescription: 'Finalisez en toute sécurité votre achat de sérums naturels. Livraison rapide en France et Belgique.'
|
||||||
|
},
|
||||||
|
|
||||||
|
blog: {
|
||||||
|
primary: [
|
||||||
|
'conseils soin visage',
|
||||||
|
'soin naturel peau',
|
||||||
|
'conseils anti-âge'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'comment utiliser sérum',
|
||||||
|
'routine soin visage',
|
||||||
|
'ingrédients naturels',
|
||||||
|
'soin peau mature'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'quelles huiles sont meilleures pour visage',
|
||||||
|
'comment réduire rides naturellement',
|
||||||
|
'routine soin quotidienne',
|
||||||
|
'alternative naturelle au rétinol'
|
||||||
|
],
|
||||||
|
metaTitle: 'Blog | Conseils Soin Visage | ManoonOils',
|
||||||
|
metaDescription: 'Conseils d\'experts pour le soin du visage, alternatives naturelles et guides pour une peau saine et éclatante. Lisez notre blog.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
categories: {
|
||||||
|
antiAging: [
|
||||||
|
'sérum anti-âge',
|
||||||
|
'sérum anti-rides',
|
||||||
|
'soin anti-âge',
|
||||||
|
'anti-âge naturel',
|
||||||
|
'sérum peau mature',
|
||||||
|
'cosmétique anti-âge'
|
||||||
|
],
|
||||||
|
hydration: [
|
||||||
|
'sérum hydratant',
|
||||||
|
'sérum peau sèche',
|
||||||
|
'hydratation visage',
|
||||||
|
'hydratation peau',
|
||||||
|
'sérum peau déshydratée'
|
||||||
|
],
|
||||||
|
glow: [
|
||||||
|
'sérum éclat',
|
||||||
|
'sérum radiance',
|
||||||
|
'peau éclatante',
|
||||||
|
'sérum luminosité',
|
||||||
|
'glow healthy'
|
||||||
|
],
|
||||||
|
sensitive: [
|
||||||
|
'sérum peau sensible',
|
||||||
|
'soin doux visage',
|
||||||
|
'sérum sans parfum',
|
||||||
|
'cosmétique hypoallergénique',
|
||||||
|
'sérum rosacée'
|
||||||
|
],
|
||||||
|
natural: [
|
||||||
|
'sérum naturel',
|
||||||
|
'sérum végétal',
|
||||||
|
'sérum ingrédients naturels',
|
||||||
|
'cosmétique naturelle',
|
||||||
|
'sérum fait maison'
|
||||||
|
],
|
||||||
|
organic: [
|
||||||
|
'sérum bio',
|
||||||
|
'sérum écologique',
|
||||||
|
'cosmétique bio',
|
||||||
|
'certifié bio',
|
||||||
|
'sérum éco'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
educational: [
|
||||||
|
'comment utiliser sérum visage',
|
||||||
|
'différence entre sérum et crème',
|
||||||
|
'comment reconnaître cosmétique naturel qualité',
|
||||||
|
'ordre application produits soin visage',
|
||||||
|
'comment lire étiquette produit cosmétique'
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
'avantages utilisation sérums naturels',
|
||||||
|
'pourquoi choisir cosmétique bio',
|
||||||
|
'avantages huile argan peau',
|
||||||
|
'huile rose musquée rides',
|
||||||
|
'squalane - tout ce qu\'il faut savoir'
|
||||||
|
],
|
||||||
|
comparison: [
|
||||||
|
'cosmétique naturelle vs synthétique',
|
||||||
|
'sérum ou crème - lequel est mieux',
|
||||||
|
'rétinol vs bakuchiol',
|
||||||
|
'peeling chimique vs enzymatique',
|
||||||
|
'peau avant après sérums naturels'
|
||||||
|
],
|
||||||
|
ingredients: [
|
||||||
|
'propriétés huile argan',
|
||||||
|
'huile jojoba visage',
|
||||||
|
'vitamine C cosmétique',
|
||||||
|
'acide hyaluronique naturel',
|
||||||
|
'certifications éco cosmétique'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
competitors: {
|
||||||
|
brands: [
|
||||||
|
'The Ordinary',
|
||||||
|
'Paula\'s Choice',
|
||||||
|
'La Roche Posay',
|
||||||
|
'Vichy',
|
||||||
|
'L\'Oreal',
|
||||||
|
'Garnier',
|
||||||
|
'Nuxe',
|
||||||
|
'Caudalie',
|
||||||
|
'Drunk Elephant',
|
||||||
|
'SkinCeuticals',
|
||||||
|
'Sunday Riley',
|
||||||
|
'Tata Harper',
|
||||||
|
'Weleda',
|
||||||
|
'Sante',
|
||||||
|
'Cattier',
|
||||||
|
'Coco\'solis'
|
||||||
|
],
|
||||||
|
comparisons: [
|
||||||
|
'manoonoils vs the ordinary',
|
||||||
|
'sérum naturel vs parapharmacie',
|
||||||
|
'cosmétique artisanale vs commerciale',
|
||||||
|
'sérum sans produits chimiques vs standard'
|
||||||
|
],
|
||||||
|
alternatives: [
|
||||||
|
'alternative à The Ordinary',
|
||||||
|
'alternative naturelle au rétinol',
|
||||||
|
'alternative abordable à SkinCeuticals',
|
||||||
|
'produit artisanal au lieu d\'importé',
|
||||||
|
'alternative sérum sans silicone'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default frenchKeywords;
|
||||||
269
src/lib/seo/keywords/locales/sr.ts
Normal file
269
src/lib/seo/keywords/locales/sr.ts
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
import { LocaleKeywords } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serbian (SR) SEO Keywords Configuration
|
||||||
|
* Primary market: Serbia
|
||||||
|
* Language: Serbian (Latin script)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const serbianKeywords: LocaleKeywords = {
|
||||||
|
locale: 'sr',
|
||||||
|
|
||||||
|
brand: {
|
||||||
|
companyName: 'ManoonOils',
|
||||||
|
tagline: 'Premium prirodni anti age serumi i ulja za lice, kožu i kosu',
|
||||||
|
category: 'prirodna kozmetika',
|
||||||
|
valueProposition: 'ručno rađeni proizvodi od prirodnih sastojaka bez hemikalija'
|
||||||
|
},
|
||||||
|
|
||||||
|
pages: {
|
||||||
|
home: {
|
||||||
|
primary: [
|
||||||
|
'prirodni serum za lice',
|
||||||
|
'organska kozmetika srbija',
|
||||||
|
'anti age serum prirodni'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'prirodna ulja za negu lica',
|
||||||
|
'domaća kozmetika',
|
||||||
|
'serum bez hemikalija',
|
||||||
|
'prirodna nega kože'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'najbolji prirodni serum za zrelu kožu',
|
||||||
|
'gde kupiti organsku kozmetiku u srbiji',
|
||||||
|
'prirodni anti age proizvodi za lice',
|
||||||
|
'serum za lice sa prirodnim sastojcima',
|
||||||
|
'ručno rađena kozmetika beograd'
|
||||||
|
],
|
||||||
|
metaTitle: 'ManoonOils | Premium prirodni serum za lice | Organska kozmetika Srbija',
|
||||||
|
metaDescription: 'Otkrijte našu kolekciju premium prirodnih seruma za lice. Anti age, hidratacija i negovana koža bez hemikalija. Ručno rađeni proizvodi u Srbiji.'
|
||||||
|
},
|
||||||
|
|
||||||
|
products: {
|
||||||
|
primary: [
|
||||||
|
'prirodni serum za lice prodaja',
|
||||||
|
'organski proizvodi za negu lica',
|
||||||
|
'anti age serum prirodni'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'serum protiv bora',
|
||||||
|
'serum za sjaj kože',
|
||||||
|
'prirodna ulja za lice',
|
||||||
|
'serum bez parabena'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'prirodni serum za suvu kožu lica',
|
||||||
|
'organski anti age serum cena',
|
||||||
|
'serum za lice sa vitaminom C',
|
||||||
|
'prirodni serum za osetljivu kožu',
|
||||||
|
'gde kupiti prirodni serum u srbiji'
|
||||||
|
],
|
||||||
|
metaTitle: 'Prirodni serum za lice | Organska kozmetika | ManoonOils',
|
||||||
|
metaDescription: 'Pregledajte našu kolekciju premium prirodnih seruma za lice. Anti age, hidratacija i negovana koža bez hemikalija.'
|
||||||
|
},
|
||||||
|
|
||||||
|
product: {
|
||||||
|
primary: [
|
||||||
|
'{{productName}} serum',
|
||||||
|
'prirodni serum za lice',
|
||||||
|
'organska kozmetika'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'serum protiv bora',
|
||||||
|
'anti age serum',
|
||||||
|
'prirodna nega lica',
|
||||||
|
'serum bez hemikalija'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'{{productName}} iskustva',
|
||||||
|
'{{productName}} cena',
|
||||||
|
'{{productName}} gde kupiti',
|
||||||
|
'{{productName}} rezultati',
|
||||||
|
'najbolji serum za {{concern}}'
|
||||||
|
],
|
||||||
|
metaTitle: '{{productName}} | Prirodni serum za lice | ManoonOils',
|
||||||
|
metaDescription: '{{productName}} - premium prirodni serum za {{concern}}. {{benefits}}. Bez hemikalija, ručno rađen u Srbiji.'
|
||||||
|
},
|
||||||
|
|
||||||
|
about: {
|
||||||
|
primary: [
|
||||||
|
'o nama manoonoils',
|
||||||
|
'prirodna kozmetika srbija',
|
||||||
|
'domaći proizvođač kozmetike'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'naša priča',
|
||||||
|
'misija i vizija',
|
||||||
|
'prirodni sastojci',
|
||||||
|
'ručna izrada'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'ko stoji iza manoonoils',
|
||||||
|
'zašto izabrati prirodnu kozmetiku',
|
||||||
|
'kako nastaju naši proizvodi',
|
||||||
|
'etička proizvodnja kozmetike'
|
||||||
|
],
|
||||||
|
metaTitle: 'O nama | ManoonOils | Prirodna kozmetika Srbija',
|
||||||
|
metaDescription: 'Upoznajte ManoonOils - domaćeg proizvođača premium prirodnih seruma. Naša priča, misija i posvećenost kvalitetu bez kompromisa.'
|
||||||
|
},
|
||||||
|
|
||||||
|
contact: {
|
||||||
|
primary: [
|
||||||
|
'kontakt manoonoils',
|
||||||
|
'kupiti prirodni serum',
|
||||||
|
'saradnja kozmetika'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'prodaja prirodne kozmetike',
|
||||||
|
'veleprodaja serum',
|
||||||
|
'distributeri srbija'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'kako naručiti manoonoils',
|
||||||
|
'kontakt telefon manoonoils',
|
||||||
|
'gde se mogu kupiti proizvodi',
|
||||||
|
'saradnja sa manoonoils'
|
||||||
|
],
|
||||||
|
metaTitle: 'Kontakt | ManoonOils | Kupite prirodni serum',
|
||||||
|
metaDescription: 'Kontaktirajte nas za narudžbine, pitanja o proizvodima ili poslovnu saradnju. ManoonOils - prirodna kozmetika Srbija.'
|
||||||
|
},
|
||||||
|
|
||||||
|
checkout: {
|
||||||
|
primary: [],
|
||||||
|
secondary: [],
|
||||||
|
longTail: [],
|
||||||
|
metaTitle: 'Završite kupovinu | ManoonOils',
|
||||||
|
metaDescription: 'Bezbedno završite vašu kupovinu prirodnih seruma. Plaćanje pouzećem. Brza isporuka širom Srbije.'
|
||||||
|
},
|
||||||
|
|
||||||
|
blog: {
|
||||||
|
primary: [
|
||||||
|
'saveti za negu lica',
|
||||||
|
'prirodna nega kože',
|
||||||
|
'anti aging saveti'
|
||||||
|
],
|
||||||
|
secondary: [
|
||||||
|
'kako koristiti serum',
|
||||||
|
'rutina nege lica',
|
||||||
|
'prirodni sastojci',
|
||||||
|
'nega zrele kože'
|
||||||
|
],
|
||||||
|
longTail: [
|
||||||
|
'koja ulja su najbolja za lice',
|
||||||
|
'kako smanjiti bore prirodnim putem',
|
||||||
|
'dnevna rutina nege kože',
|
||||||
|
'prirodna alternativa retinolu'
|
||||||
|
],
|
||||||
|
metaTitle: 'Blog | Saveti za negu lica | ManoonOils',
|
||||||
|
metaDescription: 'Ekspertni saveti za negu lica, prirodne alternative i vodiči za zdravu, negovanu kožu. Čitajte naš blog.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
categories: {
|
||||||
|
antiAging: [
|
||||||
|
'anti age serum',
|
||||||
|
'serum protiv bora',
|
||||||
|
'serum protiv starenja',
|
||||||
|
'prirodni anti age',
|
||||||
|
'serum za zrelu kožu',
|
||||||
|
'anti aging kozmetika'
|
||||||
|
],
|
||||||
|
hydration: [
|
||||||
|
'hidratantni serum',
|
||||||
|
'serum za suvu kožu',
|
||||||
|
'vlaga za lice',
|
||||||
|
'hidratacija lica',
|
||||||
|
'serum za dehidriranu kožu'
|
||||||
|
],
|
||||||
|
glow: [
|
||||||
|
'serum za sjaj kože',
|
||||||
|
'radiance serum',
|
||||||
|
'sjajna koža',
|
||||||
|
'serum za blistavost',
|
||||||
|
'healthy glow'
|
||||||
|
],
|
||||||
|
sensitive: [
|
||||||
|
'serum za osetljivu kožu',
|
||||||
|
'nežna nega lica',
|
||||||
|
'bez parfema serum',
|
||||||
|
'hipoalergena kozmetika',
|
||||||
|
'serum za kuperozu'
|
||||||
|
],
|
||||||
|
natural: [
|
||||||
|
'prirodni serum',
|
||||||
|
'biljni serum',
|
||||||
|
'serum od prirodnih sastojaka',
|
||||||
|
'prirodna kozmetika',
|
||||||
|
'domaći serum'
|
||||||
|
],
|
||||||
|
organic: [
|
||||||
|
'organski serum',
|
||||||
|
'bio serum',
|
||||||
|
'organska kozmetika',
|
||||||
|
'certificirana organska',
|
||||||
|
'eko serum'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
educational: [
|
||||||
|
'kako koristiti serum za lice',
|
||||||
|
'koja je razlika između seruma i kreme',
|
||||||
|
'kako prepoznati kvalitetnu prirodnu kozmetiku',
|
||||||
|
'redosled nanošenja proizvoda za negu lica',
|
||||||
|
'kako čitati deklaraciju kozmetičkih proizvoda'
|
||||||
|
],
|
||||||
|
benefits: [
|
||||||
|
'prednosti korišćenja prirodnih seruma',
|
||||||
|
'zašto izabrati organsku kozmetiku',
|
||||||
|
'benefiti arganovog ulja za kožu',
|
||||||
|
'ulje semena divlje ruže za bore',
|
||||||
|
'squalane - sve što treba da znate'
|
||||||
|
],
|
||||||
|
comparison: [
|
||||||
|
'prirodna vs sintetička kozmetika',
|
||||||
|
'serum ili krema - šta je bolje',
|
||||||
|
'retinol vs bakuchiol',
|
||||||
|
'hemijski piling vs enzimski',
|
||||||
|
'koža pre i posle prirodnih seruma'
|
||||||
|
],
|
||||||
|
ingredients: [
|
||||||
|
'arganovo ulje svojstva',
|
||||||
|
'ulje jojoba za lice',
|
||||||
|
'vitamin C u kozmetici',
|
||||||
|
'hijaluronska kiselina prirodna',
|
||||||
|
'eko sertifikati kozmetike'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
competitors: {
|
||||||
|
brands: [
|
||||||
|
'The Ordinary',
|
||||||
|
'Paula\'s Choice',
|
||||||
|
'La Roche Posay',
|
||||||
|
'Vichy',
|
||||||
|
'L\'Oreal',
|
||||||
|
'Garnier',
|
||||||
|
'Nuxe',
|
||||||
|
'Caudalie',
|
||||||
|
'Drunk Elephant',
|
||||||
|
'SkinCeuticals'
|
||||||
|
],
|
||||||
|
comparisons: [
|
||||||
|
'manoonoils vs the ordinary',
|
||||||
|
'prirodni serum vs drogerijski',
|
||||||
|
'domaća kozmetika vs uvozna',
|
||||||
|
'serum bez hemikalija vs standardni'
|
||||||
|
],
|
||||||
|
alternatives: [
|
||||||
|
'alternativa za the ordinary',
|
||||||
|
'prirodna alternativa za retinol',
|
||||||
|
'jeftinija alternativa za skinceuticals',
|
||||||
|
'domaći proizvod umesto uvoznog',
|
||||||
|
'serum bez silikona alternativa'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default serbianKeywords;
|
||||||
77
src/lib/seo/keywords/types.ts
Normal file
77
src/lib/seo/keywords/types.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* SEO Keywords Type Definitions
|
||||||
|
* Centralized type system for localized SEO keywords
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Locale = 'sr' | 'en' | 'de' | 'fr';
|
||||||
|
|
||||||
|
export interface BrandKeywords {
|
||||||
|
companyName: string;
|
||||||
|
tagline: string;
|
||||||
|
category: string;
|
||||||
|
valueProposition: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageKeywords {
|
||||||
|
primary: string[]; // 2-3 main keywords for page
|
||||||
|
secondary: string[]; // 3-5 supporting keywords
|
||||||
|
longTail: string[]; // 5-10 specific phrases
|
||||||
|
metaTitle: string; // Template for meta title
|
||||||
|
metaDescription: string; // Template for meta description
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductCategoryKeywords {
|
||||||
|
antiAging: string[];
|
||||||
|
hydration: string[];
|
||||||
|
glow: string[];
|
||||||
|
sensitive: string[];
|
||||||
|
natural: string[];
|
||||||
|
organic: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContentKeywords {
|
||||||
|
educational: string[]; // "how to", "guide" topics
|
||||||
|
benefits: string[]; // "benefits of" topics
|
||||||
|
comparison: string[]; // "vs", "alternative" topics
|
||||||
|
ingredients: string[]; // Ingredient-focused content
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompetitorKeywords {
|
||||||
|
brands: string[]; // Competitor brand names
|
||||||
|
comparisons: string[]; // "vs" phrases
|
||||||
|
alternatives: string[]; // "alternative to" phrases
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocaleKeywords {
|
||||||
|
locale: Locale;
|
||||||
|
brand: BrandKeywords;
|
||||||
|
pages: {
|
||||||
|
home: PageKeywords;
|
||||||
|
products: PageKeywords;
|
||||||
|
product: PageKeywords;
|
||||||
|
about: PageKeywords;
|
||||||
|
contact: PageKeywords;
|
||||||
|
checkout: PageKeywords;
|
||||||
|
blog: PageKeywords;
|
||||||
|
};
|
||||||
|
categories: ProductCategoryKeywords;
|
||||||
|
content: ContentKeywords;
|
||||||
|
competitors: CompetitorKeywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeywordStrategy {
|
||||||
|
density: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
optimal: number;
|
||||||
|
};
|
||||||
|
placement: {
|
||||||
|
title: boolean;
|
||||||
|
h1: boolean;
|
||||||
|
h2: boolean;
|
||||||
|
firstParagraph: boolean;
|
||||||
|
metaDescription: boolean;
|
||||||
|
altText: boolean;
|
||||||
|
};
|
||||||
|
variations: boolean; // Use keyword variations
|
||||||
|
}
|
||||||
148
src/lib/seo/keywords/utils/getKeywords.ts
Normal file
148
src/lib/seo/keywords/utils/getKeywords.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { Locale, LocaleKeywords } from '../types';
|
||||||
|
import { serbianKeywords } from '../locales/sr';
|
||||||
|
import { englishKeywords } from '../locales/en';
|
||||||
|
import { germanKeywords } from '../locales/de';
|
||||||
|
import { frenchKeywords } from '../locales/fr';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for loaded keywords to avoid repeated imports
|
||||||
|
*/
|
||||||
|
const keywordsCache: Record<Locale, LocaleKeywords | null> = {
|
||||||
|
sr: null,
|
||||||
|
en: null,
|
||||||
|
de: null,
|
||||||
|
fr: null
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all SEO keywords for a specific locale
|
||||||
|
* Uses caching for performance
|
||||||
|
*
|
||||||
|
* @param locale - The locale code ('sr', 'en', 'de', 'fr')
|
||||||
|
* @returns LocaleKeywords object with all keywords for that locale
|
||||||
|
* @example
|
||||||
|
* const keywords = getKeywords('sr');
|
||||||
|
* console.log(keywords.pages.home.primary); // ['prirodni serum za lice', ...]
|
||||||
|
*/
|
||||||
|
export function getKeywords(locale: Locale): LocaleKeywords {
|
||||||
|
// Return from cache if available
|
||||||
|
if (keywordsCache[locale]) {
|
||||||
|
return keywordsCache[locale]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load keywords based on locale
|
||||||
|
const keywordsMap: Record<Locale, LocaleKeywords> = {
|
||||||
|
sr: serbianKeywords,
|
||||||
|
en: englishKeywords,
|
||||||
|
de: germanKeywords,
|
||||||
|
fr: frenchKeywords
|
||||||
|
};
|
||||||
|
|
||||||
|
const keywords = keywordsMap[locale];
|
||||||
|
|
||||||
|
// Cache for future use
|
||||||
|
keywordsCache[locale] = keywords;
|
||||||
|
|
||||||
|
return keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get keywords for a specific page type
|
||||||
|
* Convenience function for page-level keyword access
|
||||||
|
*
|
||||||
|
* @param locale - The locale code
|
||||||
|
* @param pageType - The page type ('home', 'products', 'product', 'about', 'contact', 'checkout', 'blog')
|
||||||
|
* @returns PageKeywords for the specified page
|
||||||
|
* @example
|
||||||
|
* const homeKeywords = getPageKeywords('sr', 'home');
|
||||||
|
* console.log(homeKeywords.primary); // Primary keywords for homepage
|
||||||
|
*/
|
||||||
|
export function getPageKeywords(
|
||||||
|
locale: Locale,
|
||||||
|
pageType: keyof LocaleKeywords['pages']
|
||||||
|
) {
|
||||||
|
const keywords = getKeywords(locale);
|
||||||
|
return keywords.pages[pageType];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get category-specific keywords
|
||||||
|
*
|
||||||
|
* @param locale - The locale code
|
||||||
|
* @param category - The category key ('antiAging', 'hydration', 'glow', 'sensitive', 'natural', 'organic')
|
||||||
|
* @returns Array of keywords for that category
|
||||||
|
*/
|
||||||
|
export function getCategoryKeywords(
|
||||||
|
locale: Locale,
|
||||||
|
category: keyof LocaleKeywords['categories']
|
||||||
|
): string[] {
|
||||||
|
const keywords = getKeywords(locale);
|
||||||
|
return keywords.categories[category];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get content topic keywords for blog/article generation
|
||||||
|
*
|
||||||
|
* @param locale - The locale code
|
||||||
|
* @param contentType - Type of content ('educational', 'benefits', 'comparison', 'ingredients')
|
||||||
|
* @returns Array of content topic keywords
|
||||||
|
*/
|
||||||
|
export function getContentKeywords(
|
||||||
|
locale: Locale,
|
||||||
|
contentType: keyof LocaleKeywords['content']
|
||||||
|
): string[] {
|
||||||
|
const keywords = getKeywords(locale);
|
||||||
|
return keywords.content[contentType];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get competitor keywords for comparison content
|
||||||
|
*
|
||||||
|
* @param locale - The locale code
|
||||||
|
* @param competitorType - Type of competitor data ('brands', 'comparisons', 'alternatives')
|
||||||
|
* @returns Array of competitor-related keywords
|
||||||
|
*/
|
||||||
|
export function getCompetitorKeywords(
|
||||||
|
locale: Locale,
|
||||||
|
competitorType: keyof LocaleKeywords['competitors']
|
||||||
|
): string[] {
|
||||||
|
const keywords = getKeywords(locale);
|
||||||
|
return keywords.competitors[competitorType];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get brand information for the locale
|
||||||
|
*
|
||||||
|
* @param locale - The locale code
|
||||||
|
* @returns BrandKeywords with localized tagline, category, etc.
|
||||||
|
*/
|
||||||
|
export function getBrandKeywords(locale: Locale) {
|
||||||
|
const keywords = getKeywords(locale);
|
||||||
|
return keywords.brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the keywords cache (useful for testing or hot-reloading)
|
||||||
|
*/
|
||||||
|
export function clearKeywordsCache(): void {
|
||||||
|
keywordsCache.sr = null;
|
||||||
|
keywordsCache.en = null;
|
||||||
|
keywordsCache.de = null;
|
||||||
|
keywordsCache.fr = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available locales
|
||||||
|
*/
|
||||||
|
export function getAvailableLocales(): Locale[] {
|
||||||
|
return ['sr', 'en', 'de', 'fr'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if a locale is supported
|
||||||
|
*/
|
||||||
|
export function isValidLocale(locale: string): locale is Locale {
|
||||||
|
return ['sr', 'en', 'de', 'fr'].includes(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getKeywords;
|
||||||
84
src/lib/seo/schema/breadcrumbSchema.ts
Normal file
84
src/lib/seo/schema/breadcrumbSchema.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { BreadcrumbListSchema } from './types';
|
||||||
|
|
||||||
|
interface BreadcrumbItem {
|
||||||
|
name: string;
|
||||||
|
url?: string; // Optional for last item (current page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate BreadcrumbList schema (JSON-LD)
|
||||||
|
* Pure function - takes breadcrumb items, returns schema object
|
||||||
|
*
|
||||||
|
* @param items - Array of breadcrumb items with name and optional URL
|
||||||
|
* @returns BreadcrumbListSchema object
|
||||||
|
* @example
|
||||||
|
* const breadcrumbs = [
|
||||||
|
* { name: 'Home', url: 'https://example.com' },
|
||||||
|
* { name: 'Products', url: 'https://example.com/products' },
|
||||||
|
* { name: 'Product Name' } // Current page (no URL)
|
||||||
|
* ];
|
||||||
|
* const schema = generateBreadcrumbSchema(breadcrumbs);
|
||||||
|
*/
|
||||||
|
export function generateBreadcrumbSchema(
|
||||||
|
items: BreadcrumbItem[]
|
||||||
|
): BreadcrumbListSchema {
|
||||||
|
return {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'BreadcrumbList',
|
||||||
|
itemListElement: items.map((item, index) => ({
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: index + 1,
|
||||||
|
name: item.name,
|
||||||
|
...(item.url && { item: item.url }), // Only include item if URL exists
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate standard breadcrumbs for product pages
|
||||||
|
*
|
||||||
|
* @param baseUrl - Site base URL
|
||||||
|
* @param locale - Locale code
|
||||||
|
* @param productName - Product name
|
||||||
|
* @param productSlug - Product slug
|
||||||
|
* @returns BreadcrumbListSchema object
|
||||||
|
*/
|
||||||
|
export function generateProductBreadcrumbs(
|
||||||
|
baseUrl: string,
|
||||||
|
locale: string,
|
||||||
|
productName: string,
|
||||||
|
productSlug: string
|
||||||
|
): BreadcrumbListSchema {
|
||||||
|
const localePrefix = locale === 'sr' ? '' : `/${locale}`;
|
||||||
|
|
||||||
|
const items: BreadcrumbItem[] = [
|
||||||
|
{ name: 'Home', url: `${baseUrl}${localePrefix || '/'}` },
|
||||||
|
{ name: 'Products', url: `${baseUrl}${localePrefix}/products` },
|
||||||
|
{ name: productName }, // Current page
|
||||||
|
];
|
||||||
|
|
||||||
|
return generateBreadcrumbSchema(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate breadcrumbs for static pages
|
||||||
|
*
|
||||||
|
* @param baseUrl - Site base URL
|
||||||
|
* @param locale - Locale code
|
||||||
|
* @param pageName - Current page name
|
||||||
|
* @returns BreadcrumbListSchema object
|
||||||
|
*/
|
||||||
|
export function generatePageBreadcrumbs(
|
||||||
|
baseUrl: string,
|
||||||
|
locale: string,
|
||||||
|
pageName: string
|
||||||
|
): BreadcrumbListSchema {
|
||||||
|
const localePrefix = locale === 'sr' ? '' : `/${locale}`;
|
||||||
|
|
||||||
|
const items: BreadcrumbItem[] = [
|
||||||
|
{ name: 'Home', url: `${baseUrl}${localePrefix || '/'}` },
|
||||||
|
{ name: pageName }, // Current page
|
||||||
|
];
|
||||||
|
|
||||||
|
return generateBreadcrumbSchema(items);
|
||||||
|
}
|
||||||
31
src/lib/seo/schema/index.ts
Normal file
31
src/lib/seo/schema/index.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* SEO Schema Module
|
||||||
|
* JSON-LD structured data generation for SEO
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Types
|
||||||
|
export type {
|
||||||
|
ProductSchema,
|
||||||
|
ReviewSchema,
|
||||||
|
OrganizationSchema,
|
||||||
|
WebSiteSchema,
|
||||||
|
BreadcrumbListSchema,
|
||||||
|
SchemaType,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
// Schema generators (pure functions)
|
||||||
|
export {
|
||||||
|
generateProductSchema,
|
||||||
|
generateCategorizedProductSchema,
|
||||||
|
} from './productSchema';
|
||||||
|
|
||||||
|
export {
|
||||||
|
generateOrganizationSchema,
|
||||||
|
generateWebSiteSchema,
|
||||||
|
} from './organizationSchema';
|
||||||
|
|
||||||
|
export {
|
||||||
|
generateBreadcrumbSchema,
|
||||||
|
generateProductBreadcrumbs,
|
||||||
|
generatePageBreadcrumbs,
|
||||||
|
} from './breadcrumbSchema';
|
||||||
79
src/lib/seo/schema/organizationSchema.ts
Normal file
79
src/lib/seo/schema/organizationSchema.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { OrganizationSchema, WebSiteSchema } from './types';
|
||||||
|
import { getBrandKeywords } from '../keywords';
|
||||||
|
import { Locale } from '../keywords/types';
|
||||||
|
|
||||||
|
interface OrganizationData {
|
||||||
|
logoUrl: string;
|
||||||
|
socialProfiles?: string[];
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Organization schema (JSON-LD)
|
||||||
|
* Pure function - takes data, returns schema object
|
||||||
|
*
|
||||||
|
* @param baseUrl - Site base URL
|
||||||
|
* @param locale - Locale code
|
||||||
|
* @param data - Organization data (logo, social links, etc.)
|
||||||
|
* @returns OrganizationSchema object
|
||||||
|
*/
|
||||||
|
export function generateOrganizationSchema(
|
||||||
|
baseUrl: string,
|
||||||
|
locale: Locale,
|
||||||
|
data: OrganizationData
|
||||||
|
): OrganizationSchema {
|
||||||
|
const brandKeywords = getBrandKeywords(locale);
|
||||||
|
|
||||||
|
const schema: OrganizationSchema = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: brandKeywords.companyName,
|
||||||
|
url: baseUrl,
|
||||||
|
logo: data.logoUrl,
|
||||||
|
description: brandKeywords.tagline,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add social profiles if provided
|
||||||
|
if (data.socialProfiles && data.socialProfiles.length > 0) {
|
||||||
|
schema.sameAs = data.socialProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add contact point if email provided
|
||||||
|
if (data.email) {
|
||||||
|
schema.contactPoint = [{
|
||||||
|
'@type': 'ContactPoint',
|
||||||
|
contactType: 'customer service',
|
||||||
|
email: data.email,
|
||||||
|
availableLanguage: [locale.toUpperCase()],
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate WebSite schema (JSON-LD)
|
||||||
|
* Includes search action for site search
|
||||||
|
*
|
||||||
|
* @param baseUrl - Site base URL
|
||||||
|
* @param locale - Locale code
|
||||||
|
* @returns WebSiteSchema object
|
||||||
|
*/
|
||||||
|
export function generateWebSiteSchema(
|
||||||
|
baseUrl: string,
|
||||||
|
locale: Locale
|
||||||
|
): WebSiteSchema {
|
||||||
|
const brandKeywords = getBrandKeywords(locale);
|
||||||
|
|
||||||
|
return {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'WebSite',
|
||||||
|
name: brandKeywords.companyName,
|
||||||
|
url: baseUrl,
|
||||||
|
potentialAction: {
|
||||||
|
'@type': 'SearchAction',
|
||||||
|
target: `${baseUrl}/search?q={search_term_string}`,
|
||||||
|
'query-input': 'required name=search_term_string',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
104
src/lib/seo/schema/productSchema.ts
Normal file
104
src/lib/seo/schema/productSchema.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
85
src/lib/seo/schema/types.ts
Normal file
85
src/lib/seo/schema/types.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* JSON-LD Schema Types
|
||||||
|
* TypeScript definitions for structured data schemas
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ProductSchema {
|
||||||
|
'@context': 'https://schema.org';
|
||||||
|
'@type': 'Product';
|
||||||
|
name: string;
|
||||||
|
image: string[];
|
||||||
|
description: string;
|
||||||
|
sku?: string;
|
||||||
|
brand: {
|
||||||
|
'@type': 'Brand';
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
offers?: {
|
||||||
|
'@type': 'Offer';
|
||||||
|
url: string;
|
||||||
|
price: string;
|
||||||
|
priceCurrency: string;
|
||||||
|
availability: string;
|
||||||
|
itemCondition: string;
|
||||||
|
};
|
||||||
|
aggregateRating?: {
|
||||||
|
'@type': 'AggregateRating';
|
||||||
|
ratingValue: string;
|
||||||
|
reviewCount: string;
|
||||||
|
};
|
||||||
|
review?: ReviewSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReviewSchema {
|
||||||
|
'@type': 'Review';
|
||||||
|
author: {
|
||||||
|
'@type': 'Person';
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
reviewRating: {
|
||||||
|
'@type': 'Rating';
|
||||||
|
ratingValue: string;
|
||||||
|
};
|
||||||
|
reviewBody: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrganizationSchema {
|
||||||
|
'@context': 'https://schema.org';
|
||||||
|
'@type': 'Organization';
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
logo: string;
|
||||||
|
description?: string;
|
||||||
|
sameAs?: string[];
|
||||||
|
contactPoint?: {
|
||||||
|
'@type': 'ContactPoint';
|
||||||
|
contactType: string;
|
||||||
|
email?: string;
|
||||||
|
availableLanguage?: string[];
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebSiteSchema {
|
||||||
|
'@context': 'https://schema.org';
|
||||||
|
'@type': 'WebSite';
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
potentialAction?: {
|
||||||
|
'@type': 'SearchAction';
|
||||||
|
target: string;
|
||||||
|
'query-input': string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BreadcrumbListSchema {
|
||||||
|
'@context': 'https://schema.org';
|
||||||
|
'@type': 'BreadcrumbList';
|
||||||
|
itemListElement: {
|
||||||
|
'@type': 'ListItem';
|
||||||
|
position: number;
|
||||||
|
name: string;
|
||||||
|
item?: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SchemaType = ProductSchema | OrganizationSchema | WebSiteSchema | BreadcrumbListSchema;
|
||||||
Reference in New Issue
Block a user