Compare commits

...

6 Commits

Author SHA1 Message Date
Flux CD
cca6f44139 Merge branch 'master' into feature/programmatic-seo 2026-04-05 11:25:22 +00:00
Flux CD
f6cdcd86df merge: integrate latest master with Gitea Actions CI/CD 2026-04-05 06:20:16 +00:00
Unchained
6a05abc6de ci: simplify Gitea Actions workflow to use BuildKit 2026-04-05 08:12:24 +02:00
Flux CD
9058002f8d merge: integrate master deployment changes (keep BuildKit setup) 2026-04-05 05:49:31 +00:00
Unchained
590b6ca6ea fix(k8s): handle existing workspace on pod restart
Apply same fix from master branch:
- Check if workspace exists before cloning
- Fetch and reset if .git directory exists
- Clean and clone fresh if not

Prevents CrashLoopBackOff on pod restarts.
2026-04-05 05:33:17 +02:00
Unchained
f6609f07d7 feat: implement programmatic SEO solutions hub
- Add /solutions hub page with 10 category cards
- Add /solutions/by-concern directory page
- Add /solutions/by-oil directory page
- Add Solutions section to Footer with navigation links
- Add Breadcrumb component for solution pages
- Add translations for all solution pages (sr, en, de, fr)
- Fix ExitIntentDetector JSON parsing error
- Update sitemap with solution pages
- Create 3 sample solution pages with data files
2026-04-05 05:21:57 +02:00
22 changed files with 3263 additions and 8 deletions

View File

@@ -0,0 +1,206 @@
{
"slug": "best-argan-oil-for-under-eye-bags",
"oilSlug": "argan-oil",
"concernSlug": "under-eye-bags",
"pageTitle": {
"sr": "Najbolje arganovo ulje za podočnjake",
"en": "Best Argan Oil for Under Eye Bags",
"de": "Bestes Arganöl für Augenringe",
"fr": "Meilleure huile d'argan pour les cernes"
},
"metaTitle": {
"sr": "Najbolje arganovo ulje za podočnjake | Prirodno uklanjanje otoka | ManoonOils",
"en": "Best Argan Oil for Under Eye Bags | Natural Depuffing Solution | ManoonOils",
"de": "Bestes Arganöl für Augenringe | Natürliche Abschwelllösung | ManoonOils",
"fr": "Meilleure huile d'argan pour les cernes | Solution naturelle dégonflante | ManoonOils"
},
"metaDescription": {
"sr": "Saznajte zašto je arganovo ulje idealno za uklanjanje podočnjaka. Bogato vitaminom E i antioksidansima za osveženu, odmornu pojavu očiju.",
"en": "Learn why argan oil is ideal for reducing under eye bags. Rich in vitamin E and antioxidants for a refreshed, rested eye appearance.",
"de": "Erfahren Sie, warum Arganöl ideal zur Reduzierung von Augenringen ist. Reich an Vitamin E und Antioxidantien für ein erfrischtes, ausgeruhtes Augenaussehen.",
"fr": "Découvrez pourquoi l'huile d'argan est idéale pour réduire les cernes. Riche en vitamine E et antioxydants pour un regard frais et reposé."
},
"oilName": {
"sr": "Arganovo ulje",
"en": "Argan Oil",
"de": "Arganöl",
"fr": "Huile d'argan"
},
"concernName": {
"sr": "Podočnjaci",
"en": "Under Eye Bags",
"de": "Augenringe",
"fr": "Cernes"
},
"whyThisWorks": {
"sr": "Arganovo ulje je poznato kao tečno zlato iz Maroka zbog svoje izuzetne moći da hidratizira i neguje nežnu kožu oko očiju. Visoka koncentracija vitamina E i esencijalnih masnih kiselina pomaže u jačanju krvnih sudova, smanjenju zadržavanja tečnosti i poboljšanju mikrocirkulacije - ključnih faktora za uklanjanje podočnjaka.",
"en": "Argan oil is known as liquid gold from Morocco for its exceptional ability to hydrate and nourish the delicate eye area. The high concentration of vitamin E and essential fatty acids helps strengthen blood vessels, reduce fluid retention, and improve microcirculation - key factors for eliminating under eye bags.",
"de": "Arganöl ist als flüssiges Gold aus Marokko bekannt für seine außergewöhnliche Fähigkeit, den empfindlichen Augenbereich zu hydratisieren und zu pflegen. Die hohe Konzentration an Vitamin E und essenziellen Fettsäuren hilft, Blutgefäße zu stärken, Flüssigkeitsretention zu reduzieren und die Mikrozirkulation zu verbessern - Schlüsselfaktoren für die Elimination von Augenringen.",
"fr": "L'huile d'argan est connue comme l'or liquide du Maroc pour sa capacité exceptionnelle à hydrater et nourrir la zone délicate des yeux. La haute concentration en vitamine E et acides gras essentiels aide à renforcer les vaisseaux sanguins, réduire la rétention d'eau et améliorer la microcirculation - facteurs clés pour éliminer les cernes."
},
"keyBenefits": {
"sr": [
"Smanjuje zadržavanje tečnosti u predelu očiju",
"Jača krvne sudove i kapilare",
"Poboljšava mikrocirkulaciju",
"Hidratizira nežnu kožu bez iritacije",
"Smanjuje tamne krugove uz podočnjake",
"Daje odmoran, osvežen izgled"
],
"en": [
"Reduces fluid retention in the eye area",
"Strengthens blood vessels and capillaries",
"Improves microcirculation",
"Hydrates delicate skin without irritation",
"Reduces dark circles along with bags",
"Gives a rested, refreshed appearance"
],
"de": [
"Reduziert Flüssigkeitsretention im Augenbereich",
"Stärkt Blutgefäße und Kapillaren",
"Verbessert die Mikrozirkulation",
"Hydratisiert empfindliche Haut ohne Reizung",
"Reduziert Augenringe zusammen mit Tränensäcken",
"Gibt ein ausgeruhtes, erfrischtes Aussehen"
],
"fr": [
"Réduit la rétention d'eau dans la zone des yeux",
"Renforce les vaisseaux sanguins et capillaires",
"Améliore la microcirculation",
"Hydrate la peau délicate sans irritation",
"Réduit les cernes en plus des poches",
"Donne un aspect reposé et frais"
]
},
"howToApply": {
"sr": [
"Nanesite samo 1 kap ulja na četvrti prst",
"Pažljivo utapkajte oko očiju, od unutra ka spolja",
"Lagano masirajte u krugovima 30 sekundi",
"Koristite ujutru za smanjenje otoka",
"Možete staviti u frižider za dodatno osveženje",
"Budite nežni - koža oko očiju je veoma tanja"
],
"en": [
"Apply only 1 drop of oil to your ring finger",
"Gently pat around eyes, from inner to outer corner",
"Lightly massage in circles for 30 seconds",
"Use in the morning for de-puffing",
"Can be stored in fridge for extra refreshment",
"Be gentle - eye skin is much thinner"
],
"de": [
"Tragen Sie nur 1 Tropfen Öl auf den Ringfinger auf",
"Tupfen Sie sanft um die Augen, von innen nach außen",
"Massieren Sie 30 Sekunden leicht kreisförmig",
"Verwenden Sie morgens zur Abschwellung",
"Kann im Kühlschrank für zusätzliche Frische gelagert werden",
"Seien Sie sanft - die Augenhaut ist viel dünner"
],
"fr": [
"Appliquez seulement 1 goutte d'huile sur votre annulaire",
"Tapotez délicatement autour des yeux, de l'intérieur vers l'extérieur",
"Massez légèrement en cercles pendant 30 secondes",
"Utilisez le matin pour dégonfler",
"Peut être conservé au réfrigérateur pour plus de fraîcheur",
"Soyez doux - la peau des yeux est beaucoup plus fine"
]
},
"expectedResults": {
"sr": "Mnoge klijentkinje primećuju smanjenje otoka već nakon prve aplikacije zahvaljujući hidrataciji. Za trajno smanjenje podočnjaka, potrebno je 3-4 nedelje redovne upotrebe. Kombinacija sa dobrim spavanjem i hidratacijom daje najbolje rezultate.",
"en": "Many clients notice reduced puffiness after just the first application thanks to hydration. For lasting reduction of under eye bags, 3-4 weeks of regular use is needed. Combination with good sleep and hydration gives best results.",
"de": "Viele Kundinnen bemerken bereits nach der ersten Anwendung eine reduzierte Schwellung dank Hydratation. Für eine dauerhafte Reduzierung von Augenringen sind 3-4 Wochen regelmäßige Anwendung erforderlich. Die Kombination mit gutem Schlaf und Hydratation liefert die besten Ergebnisse.",
"fr": "De nombreuses clientes remarquent une réduction du gonflement dès la première application grâce à l'hydratation. Pour une réduction durable des cernes, 3-4 semaines d'utilisation régulière sont nécessaires. La combinaison avec un bon sommeil et une bonne hydratation donne les meilleurs résultats."
},
"timeframe": {
"sr": "Odmah za hidrataciju, 3-4 nedelje za smanjenje podočnjaka",
"en": "Immediate hydration, 3-4 weeks for bag reduction",
"de": "Sofortige Feuchtigkeit, 3-4 Wochen für Reduzierung",
"fr": "Hydratation immédiate, 3-4 semaines pour réduction"
},
"complementaryIngredients": [
"caffeine",
"vitamin-k",
"cucumber-extract",
"green-tea"
],
"productsToShow": [
"Manoon Eye Serum",
"Manoon 7"
],
"customerResults": [
{
"quote": {
"sr": "Kao majka troje dece, podočnjaci su bili moja stvarnost. Ovo ulje mi je vratilo osvežen izgled!",
"en": "As a mother of three, under eye bags were my reality. This oil gave me back a refreshed look!",
"de": "Als Mutter von drei Kindern waren Augenringe meine Realität. Dieses Öl gab mir ein erfrischtes Aussehen zurück!",
"fr": "En tant que mère de trois enfants, les cernes étaient ma réalité. Cette huile m'a redonné un look frais!"
},
"name": "Jelena M.",
"age": 38,
"timeframe": "3 weeks"
}
],
"faqs": [
{
"question": {
"sr": "Da li arganovo ulje može da uđe u oči?",
"en": "Can argan oil get into the eyes?",
"de": "Kann Arganöl in die Augen gelangen?",
"fr": "L'huile d'argan peut-elle entrer dans les yeux?"
},
"answer": {
"sr": "Nanesite ga pažljivo oko očiju, ne direktno na kapak. Ako uđe u oko, isperite obilno vodom. Arganaovo ulje je blago, ali kontakt sa očima može izazvati privremeno zamućenje vida.",
"en": "Apply carefully around the eyes, not directly on the eyelid. If it gets in the eye, rinse thoroughly with water. Argan oil is mild, but eye contact may cause temporary blurred vision.",
"de": "Tragen Sie es vorsichtig um die Augen auf, nicht direkt auf das Augenlid. Bei Kontakt mit dem Auge gründlich mit Wasser spülen. Arganöl ist mild, aber Augenkontakt kann vorübergehend verschwommenes Sehen verursachen.",
"fr": "Appliquez délicatement autour des yeux, pas directement sur la paupière. En cas de contact avec l'œil, rincer abondamment à l'eau. L'huile d'argan est douce, mais le contact avec les yeux peut provoquer une vision temporairement trouble."
}
},
{
"question": {
"sr": "Koliko dugo čuvati ulje u frižideru?",
"en": "How long to store the oil in the fridge?",
"de": "Wie lange das Öl im Kühlschrank lagern?",
"fr": "Combien de temps conserver l'huile au réfrigérateur?"
},
"answer": {
"sr": "Možete ga čuvati stalno u frižideru za osvežavajuću aplikaciju. Hladnoća dodatno smanjuje otok i daje prijatan osećaj pri nanošenju.",
"en": "You can store it permanently in the fridge for a refreshing application. The cold further reduces puffiness and gives a pleasant sensation when applying.",
"de": "Sie können es dauerhaft im Kühlschrank für eine erfrischende Anwendung lagern. Die Kälte reduziert weiter die Schwellung und gibt ein angenehmes Gefühl beim Auftragen.",
"fr": "Vous pouvez la conserver en permanence au réfrigérateur pour une application rafraîchissante. Le froid réduit davantage le gonflement et procure une sensation agréable lors de l'application."
}
}
],
"seoKeywords": {
"sr": {
"primary": ["arganovo ulje za podočnjake", "prirodno uklanjanje podočnjaka", "ulje protiv podočnjaka"],
"secondary": ["nega predela očiju", "tamni krugovi", "otok oko očiju"],
"longTail": ["kako ukloniti podočnjake prirodno", "najbolje ulje za podočnjake", "prirodna nega očiju"]
},
"en": {
"primary": ["argan oil for under eye bags", "natural eye bag remedy", "oil for puffy eyes"],
"secondary": ["eye area care", "dark circles", "eye puffiness"],
"longTail": ["how to remove eye bags naturally", "best oil for under eye bags", "natural eye care"]
},
"de": {
"primary": ["Arganöl für Augenringe", "natürliche Augenring-Behandlung", "Öl für geschwollene Augen"],
"secondary": ["Augenbereichspflege", "Augenringe", "Augenschwellung"],
"longTail": ["Augenringe natürlich entfernen", "bestes Öl für Augenringe", "natürliche Augenpflege"]
},
"fr": {
"primary": ["huile d'argan cernes", "remède naturel cernes", "huile pour poches sous les yeux"],
"secondary": ["soin contour des yeux", "cernes", "poches sous les yeux"],
"longTail": ["comment enlever les cernes naturellement", "meilleure huile pour cernes", "soin yeux naturel"]
}
},
"relatedPages": {
"otherOilsForSameConcern": [
"best-cucumber-oil-for-under-eye-bags",
"best-almond-oil-for-under-eye-bags"
],
"sameOilForOtherConcerns": [
"best-argan-oil-for-dry-skin",
"best-argan-oil-for-hair"
]
}
}

View File

@@ -0,0 +1,233 @@
{
"slug": "best-rosehip-oil-for-wrinkles",
"oilSlug": "rosehip-oil",
"concernSlug": "wrinkles",
"pageTitle": {
"sr": "Najbolje ulje divlje ruže protiv bora",
"en": "Best Rosehip Oil for Wrinkles",
"de": "Bestes Hagebuttenöl gegen Falten",
"fr": "Meilleure huile de rose musquée pour les rides"
},
"metaTitle": {
"sr": "Najbolje ulje divlje ruže protiv bora | Prirodno rešenje za bore | ManoonOils",
"en": "Best Rosehip Oil for Wrinkles | Natural Anti-Aging Solution | ManoonOils",
"de": "Bestes Hagebuttenöl gegen Falten | Natürliche Anti-Aging-Lösung | ManoonOils",
"fr": "Meilleure huile de rose musquée pour les rides | Solution anti-âge naturelle | ManoonOils"
},
"metaDescription": {
"sr": "Otkrijte zašto je ulje divlje ruže najbolji prirodni izbor protiv bora. Bogato vitaminom A i omega kiselinama za glatku, mladalačku kožu.",
"en": "Discover why rosehip oil is the best natural choice for wrinkles. Rich in vitamin A and omega fatty acids for smooth, youthful skin.",
"de": "Entdecken Sie, warum Hagebuttenöl die beste natürliche Wahl gegen Falten ist. Reich an Vitamin A und Omega-Fettsäuren für glatte, jugendliche Haut.",
"fr": "Découvrez pourquoi l'huile de rose musquée est le meilleur choix naturel contre les rides. Riche en vitamine A et acides gras oméga pour une peau lisse et jeune."
},
"oilName": {
"sr": "Ulje divlje ruže",
"en": "Rosehip Oil",
"de": "Hagebuttenöl",
"fr": "Huile de rose musquée"
},
"concernName": {
"sr": "Bore",
"en": "Wrinkles",
"de": "Falten",
"fr": "Rides"
},
"whyThisWorks": {
"sr": "Ulje divlje ruže sadrži prirodni trans-retinoičnu kiselinu, formu vitamina A koja stimuliše proizvodnju kolagena i ubrzava obnavljanje ćelija. Njegova jedinstvena kombinacija esencijalnih masnih kiselina prodire duboko u kožu, popravljajući oštećenu kožnu barijeru i vraćajući elastičnost.",
"en": "Rosehip oil contains natural trans-retinoic acid, a form of vitamin A that stimulates collagen production and accelerates cell renewal. Its unique blend of essential fatty acids penetrates deep into the skin, repairing damaged skin barriers and restoring elasticity.",
"de": "Hagebuttenöl enthält natürliche Trans-Retinsäure, eine Form von Vitamin A, die die Kollagenproduktion stimuliert und die Zellerneuerung beschleunigt. Seine einzigartige Mischung essenzieller Fettsäuren dringt tief in die Haut ein, repariert beschädigte Hautbarrieren und stellt die Elastizität wieder her.",
"fr": "L'huile de rose musquée contient de l'acide trans-rétinoïque naturel, une forme de vitamine A qui stimule la production de collagène et accélère le renouvellement cellulaire. Son mélange unique d'acides gras essentiels pénètre en profondeur dans la peau, répare les barrières cutanées endommagées et restaure l'élasticité."
},
"keyBenefits": {
"sr": [
"Stimuliše prirodnu proizvodnju kolagena",
"Smanjuje dubinu postojećih bora",
"Prevencija nastanka novih bora",
"Poboljšava teksturu kože",
"Ujednačava ten kože",
"Intenzivno hidratizira bez zagušivanja pora"
],
"en": [
"Stimulates natural collagen production",
"Reduces depth of existing wrinkles",
"Prevents formation of new wrinkles",
"Improves skin texture",
"Evens out skin tone",
"Intensely hydrates without clogging pores"
],
"de": [
"Stimuliert die natürliche Kollagenproduktion",
"Reduziert die Tiefe bestehender Falten",
"Verhindert die Bildung neuer Falten",
"Verbessert die Hauttextur",
"Ebnert den Teint aus",
"Intensiv feuchtigkeitsspendend ohne Poren zu verstopfen"
],
"fr": [
"Stimule la production naturelle de collagène",
"Réduit la profondeur des rides existantes",
"Prévient la formation de nouvelles rides",
"Améliore la texture de la peau",
"Unifie le teint",
"Hydrate intensément sans boucher les pores"
]
},
"howToApply": {
"sr": [
"Nanesite 2-3 kapi na očišćeno lice i vrat",
"Blago utapkajte prstima, ne trljajte",
"Fokusirajte se na područja sa borama",
"Koristite uveče za najbolje rezultate",
"Možete mešati sa hidratantnom kremom",
"Budite dosledni - rezultati za 4-6 nedelja"
],
"en": [
"Apply 2-3 drops to cleansed face and neck",
"Gently pat with fingertips, don't rub",
"Focus on wrinkle-prone areas",
"Use in the evening for best results",
"Can be mixed with moisturizer",
"Be consistent - results in 4-6 weeks"
],
"de": [
"2-3 Tropfen auf gereinigtes Gesicht und Hals auftragen",
"Sanft mit den Fingerspitzen klopfen, nicht reiben",
"Konzentrieren Sie sich auf faltenanfällige Bereiche",
"Verwenden Sie abends für beste Ergebnisse",
"Kann mit Feuchtigkeitscreme gemischt werden",
"Seien Sie konsistent - Ergebnisse nach 4-6 Wochen"
],
"fr": [
"Appliquez 2-3 gouttes sur le visage et le cou nettoyés",
"Tapotez délicatement du bout des doigts, ne frottez pas",
"Concentrez-vous sur les zones à risque de rides",
"Utilisez le soir pour de meilleurs résultats",
"Peut être mélangé avec une crème hydratante",
"Soyez constant - résultats en 4-6 semaines"
]
},
"expectedResults": {
"sr": "Većina korisnica primećuje prve rezultate nakon 2-3 nedelje redovne upotrebe - koža je mekša i hidratizovanija. Za vidljivo smanjenje bora potrebno je 6-8 nedelja dosledne upotrebe. Najbolji rezultati se postižu nakon 3 meseca.",
"en": "Most users notice first results after 2-3 weeks of regular use - skin feels softer and more hydrated. For visible wrinkle reduction, 6-8 weeks of consistent use is needed. Best results are achieved after 3 months.",
"de": "Die meisten Benutzer bemerken erste Ergebnisse nach 2-3 Wochen regelmäßiger Anwendung - die Haut fühlt sich weicher und hydratierter an. Für eine sichtbare Faltenreduktion sind 6-8 Wochen konsequenter Anwendung erforderlich. Die besten Ergebnisse werden nach 3 Monaten erzielt.",
"fr": "La plupart des utilisateurs remarquent les premiers résultats après 2-3 semaines d'utilisation régulière - la peau est plus douce et hydratée. Pour une réduction visible des rides, 6-8 semaines d'utilisation constante sont nécessaires. Les meilleurs résultats sont obtenus après 3 mois."
},
"timeframe": {
"sr": "2-3 nedelje za hidrataciju, 6-8 nedelja za bore, 3 meseca za transformaciju",
"en": "2-3 weeks for hydration, 6-8 weeks for wrinkles, 3 months for transformation",
"de": "2-3 Wochen für Feuchtigkeit, 6-8 Wochen für Falten, 3 Monate für Transformation",
"fr": "2-3 semaines pour l'hydratation, 6-8 semaines pour les rides, 3 mois pour la transformation"
},
"complementaryIngredients": [
"vitamin-e",
"hyaluronic-acid",
"niacinamide",
"squalane"
],
"productsToShow": [
"Manoon 7",
"Manoon Lux"
],
"customerResults": [
{
"quote": {
"sr": "Posle 2 meseca korišćenja, moje bore oko očiju su znatno manje vidljive. Koža je neverovatno meka i sjajna!",
"en": "After 2 months of use, my eye wrinkles are significantly less visible. My skin is incredibly soft and glowing!",
"de": "Nach 2 Monaten Anwendung sind meine Augenfalten deutlich weniger sichtbar. Meine Haut ist unglaublich weich und strahlend!",
"fr": "Après 2 mois d'utilisation, mes rides des yeux sont significativement moins visibles. Ma peau est incroyablement douce et éclatante!"
},
"name": "Marija P.",
"age": 52,
"timeframe": "2 months"
},
{
"quote": {
"sr": "Najbolje ulje koje sam ikada probala za bore. Rezultati su stvarni, ne lažna obećanja.",
"en": "Best oil I've ever tried for wrinkles. The results are real, not fake promises.",
"de": "Bestes Öl, das ich je gegen Falten ausprobiert habe. Die Ergebnisse sind real, keine leeren Versprechen.",
"fr": "Meilleure huile que j'ai jamais essayée contre les rides. Les résultats sont réels, pas de fausses promesses."
},
"name": "Ana K.",
"age": 45,
"timeframe": "6 weeks"
}
],
"faqs": [
{
"question": {
"sr": "Koliko često treba koristiti ulje divlje ruže protiv bora?",
"en": "How often should I use rosehip oil for wrinkles?",
"de": "Wie oft sollte ich Hagebuttenöl gegen Falten verwenden?",
"fr": "À quelle fréquence dois-je utiliser l'huile de rose musquée contre les rides?"
},
"answer": {
"sr": "Preporučujemo svakodnevnu upotrebu uveče na očišćenom licu. Za intenzivnju negu, možete ga koristiti i ujutru, ali uvek nanesite zaštitni faktor nakon toga.",
"en": "We recommend daily use in the evening on cleansed face. For intensive care, you can use it in the morning too, but always apply sunscreen afterwards.",
"de": "Wir empfehlen die tägliche Anwendung abends auf gereinigtem Gesicht. Für intensive Pflege können Sie es auch morgens verwenden, aber tragen Sie danach immer Sonnenschutz auf.",
"fr": "Nous recommandons une utilisation quotidienne le soir sur le visage nettoyé. Pour des soins intensifs, vous pouvez l'utiliser le matin aussi, mais appliquez toujours un écran solaire après."
}
},
{
"question": {
"sr": "Da li ulje divlje ruže izaziva iritaciju?",
"en": "Does rosehip oil cause irritation?",
"de": "Verursacht Hagebuttenöl Reizungen?",
"fr": "L'huile de rose musquée cause-t-elle des irritations?"
},
"answer": {
"sr": "Ulje divlje ruže je generalno blago i prikladno za sve tipove kože. Ipak, sadrži prirodne forme vitamina A, pa preporučujemo testiranje na malom delu kože prvo.",
"en": "Rosehip oil is generally mild and suitable for all skin types. However, it contains natural forms of vitamin A, so we recommend testing on a small skin area first.",
"de": "Hagebuttenöl ist im Allgemeinen mild und für alle Hauttypen geeignet. Es enthält jedoch natürliche Formen von Vitamin A, daher empfehlen wir, es zuerst an einer kleinen Hautstelle zu testen.",
"fr": "L'huile de rose musquée est généralement douce et adaptée à tous les types de peau. Cependant, elle contient des formes naturelles de vitamine A, nous recommandons donc de tester sur une petite zone de peau d'abord."
}
},
{
"question": {
"sr": "Kada mogu očekivati prve rezultate?",
"en": "When can I expect first results?",
"de": "Wann kann ich erste Ergebnisse erwarten?",
"fr": "Quand puis-je attendre les premiers résultats?"
},
"answer": {
"sr": "Prve rezultate u vidu hidratacije i mekoće kože možete očekivati već nakon 2-3 nedelje. Za vidljivo smanjenje bora potrebno je 6-8 nedelja redovne upotrebe.",
"en": "You can expect first results in terms of hydration and skin softness after just 2-3 weeks. For visible wrinkle reduction, 6-8 weeks of regular use is needed.",
"de": "Sie können erste Ergebnisse in Bezug auf Feuchtigkeit und Hautweichheit bereits nach 2-3 Wochen erwarten. Für eine sichtbare Faltenreduktion sind 6-8 Wochen regelmäßige Anwendung erforderlich.",
"fr": "Vous pouvez attendre les premiers résultats en termes d'hydratation et de douceur de la peau après seulement 2-3 semaines. Pour une réduction visible des rides, 6-8 semaines d'utilisation régulière sont nécessaires."
}
}
],
"seoKeywords": {
"sr": {
"primary": ["ulje divlje ruže protiv bora", "prirodno rešenje za bore", "najbolje ulje za bore"],
"secondary": ["anti-aging ulje", "prirodni retinol", "serum protiv starenja"],
"longTail": ["kako ukloniti bore prirodnim putem", "ulje divlje ruže iskustva", "najbolji serum za bore posle 40"]
},
"en": {
"primary": ["rosehip oil for wrinkles", "natural wrinkle solution", "best oil for wrinkles"],
"secondary": ["anti-aging oil", "natural retinol", "anti-aging serum"],
"longTail": ["how to remove wrinkles naturally", "rosehip oil before and after", "best wrinkle serum over 40"]
},
"de": {
"primary": ["Hagebuttenöl gegen Falten", "natürliche Faltenlösung", "bestes Öl gegen Falten"],
"secondary": ["Anti-Aging-Öl", "natürliches Retinol", "Anti-Aging-Serum"],
"longTail": ["Falten natürlich entfernen", "Hagebuttenöl Vorher Nachher", "bestes Falten-Serum über 40"]
},
"fr": {
"primary": ["huile de rose musquée rides", "solution naturelle rides", "meilleure huile anti-rides"],
"secondary": ["huile anti-âge", "rétinol naturel", "sérum anti-âge"],
"longTail": ["comment effacer les rides naturellement", "huile de rose musquée avant après", "meilleur sérum anti-rides après 40 ans"]
}
},
"relatedPages": {
"otherOilsForSameConcern": [
"best-argan-oil-for-wrinkles",
"best-marula-oil-for-wrinkles",
"best-pomegranate-oil-for-wrinkles"
],
"sameOilForOtherConcerns": [
"best-rosehip-oil-for-scars",
"best-rosehip-oil-for-hyperpigmentation",
"best-rosehip-oil-for-dry-skin"
]
}
}

View File

@@ -0,0 +1,206 @@
{
"slug": "best-sea-buckthorn-oil-for-hyperpigmentation",
"oilSlug": "sea-buckthorn-oil",
"concernSlug": "hyperpigmentation",
"pageTitle": {
"sr": "Najbolje ulje rakitovca za hiperpigmentaciju",
"en": "Best Sea Buckthorn Oil for Hyperpigmentation",
"de": "Bestes Sanddornöl für Hyperpigmentierung",
"fr": "Meilleure huile d'argousier pour l'hyperpigmentation"
},
"metaTitle": {
"sr": "Najbolje ulje rakitovca za hiperpigmentaciju | Prirodno izbeljivanje | ManoonOils",
"en": "Best Sea Buckthorn Oil for Hyperpigmentation | Natural Brightening | ManoonOils",
"de": "Bestes Sanddornöl für Hyperpigmentierung | Natürliche Aufhellung | ManoonOils",
"fr": "Meilleure huile d'argousier pour l'hyperpigmentation | Éclaircissement naturel | ManoonOils"
},
"metaDescription": {
"sr": "Otkrijte moć ulja rakitovca protiv tamnih fleka. Sa 12 puta više vitamina C od narandže za sjajnu, ujednačenu boju kože.",
"en": "Discover the power of sea buckthorn oil against dark spots. With 12x more vitamin C than oranges for bright, even skin tone.",
"de": "Entdecken Sie die Kraft von Sanddornöl gegen dunkle Flecken. Mit 12x mehr Vitamin C als Orangen für einen hellen, ebenen Teint.",
"fr": "Découvrez la puissance de l'huile d'argousier contre les taches sombres. Avec 12x plus de vitamine C que les oranges pour un teint lumineux et uniforme."
},
"oilName": {
"sr": "Ulje rakitovca",
"en": "Sea Buckthorn Oil",
"de": "Sanddornöl",
"fr": "Huile d'argousier"
},
"concernName": {
"sr": "Hiperpigmentacija",
"en": "Hyperpigmentation",
"de": "Hyperpigmentierung",
"fr": "Hyperpigmentation"
},
"whyThisWorks": {
"sr": "Ulje rakitovca je jedno od najbogatijih prirodnih izvora vitamina C na svetu - čak 12 puta više od narandže! Ova izuzetna koncentracija antioksidanasa inhibira proizvodnju melanina na mestima gde je pretjerana, postepeno izbeljujući tamne fleke. Dodatno, beta-karoten i omega masne kiseline ubrzavaju obnavljanje ćelija, dovodeći do ujednačenog tena.",
"en": "Sea buckthorn oil is one of the richest natural sources of vitamin C in the world - 12 times more than oranges! This exceptional antioxidant concentration inhibits melanin production where it's excessive, gradually lightening dark spots. Additionally, beta-carotene and omega fatty acids accelerate cell renewal, leading to an even skin tone.",
"de": "Sanddornöl ist eine der reichsten natürlichen Quellen für Vitamin C der Welt - 12-mal mehr als Orangen! Diese außergewöhnliche Antioxidantienkonzentration hemmt die Melaninproduktion dort, wo sie übermäßig ist, und hellt dunkle Flecken allmählich auf. Zusätzlich beschleunigen Beta-Karotin und Omega-Fettsäuren die Zellerneuerung, was zu einem ebenen Teint führt.",
"fr": "L'huile d'argousier est l'une des sources naturelles les plus riches en vitamine C au monde - 12 fois plus que les oranges ! Cette concentration exceptionnelle d'antioxydants inhibe la production de mélanine là où elle est excessive, éclaircissant progressivement les taches sombres. De plus, le bêta-carotène et les acides gras oméga accélèrent le renouvellement cellulaire, conduisant à un teint uniforme."
},
"keyBenefits": {
"sr": [
"Sadrži 12x više vitamina C od narandže",
"Prirodno inhibira proizvodnju melanina",
"Postepeno svetli tamne fleke i pege",
"Prevencija novih pigmentnih promena",
"Daje koži zdrav sjaj",
"Ujednačava neujednačen ten"
],
"en": [
"Contains 12x more vitamin C than oranges",
"Naturally inhibits melanin production",
"Gradually lightens dark spots and freckles",
"Prevents new pigmentation issues",
"Gives skin a healthy glow",
"Evens out uneven skin tone"
],
"de": [
"Enthält 12x mehr Vitamin C als Orangen",
"Hemmtt natürlich die Melaninproduktion",
"Helllt dunkle Flecken und Sommersprossen allmählich auf",
"Verhindert neue Pigmentierungsprobleme",
"Gibt der Haut einen gesunden Glanz",
"Ebnert unebenen Teint aus"
],
"fr": [
"Contient 12x plus de vitamine C que les oranges",
"Inhibe naturellement la production de mélanine",
"Éclaircit progressivement les taches sombres et les taches de rousseur",
"Prévient les nouveaux problèmes de pigmentation",
"Donne à la peau un éclat santé",
"Unifie le teint inégal"
]
},
"howToApply": {
"sr": [
"Nanesite samo na područja sa hiperpigmentacijom",
"Koristite kao 'tretman tačkasto' ili po celom licu",
"Mešajte sa nosiocem ulja (jojoba ili badem) 1:1",
"Koristite uveče - vit. C je fotosenzitivna",
"Uvek nanesite zaštitni faktor ujutru",
"Budite strpljivi - rezultati za 6-8 nedelja"
],
"en": [
"Apply only to hyperpigmented areas",
"Use as 'spot treatment' or all over face",
"Mix with carrier oil (jojoba or almond) 1:1",
"Use in evening - vit. C is photosensitive",
"Always apply sunscreen in the morning",
"Be patient - results in 6-8 weeks"
],
"de": [
"Nur auf hyperpigmentierte Bereiche auftragen",
"Als 'Fleckenbehandlung' oder im ganzen Gesicht verwenden",
"Mit Trägeröl (Jojoba oder Mandel) 1:1 mischen",
"Abends verwenden - Vit. C ist lichtempfindlich",
"Morgens immer Sonnenschutz auftragen",
"Geduldig sein - Ergebnisse nach 6-8 Wochen"
],
"fr": [
"Appliquer uniquement sur les zones hyperpigmentées",
"Utiliser comme 'traitement ciblé' ou sur tout le visage",
"Mélanger avec huile de support (jojoba ou amande) 1:1",
"Utiliser le soir - vit. C est photosensible",
"Toujours appliquer de la crème solaire le matin",
"Soyez patient - résultats en 6-8 semaines"
]
},
"expectedResults": {
"sr": "Zbog snažnog dejstva, ulje rakitovca zahteva strpljenje. Prve promene u sjaju kože videćete nakon 2-3 nedelje. Za vidljivo izbeljivanje tamnih fleka potrebno je 6-8 nedelja, a za kompletnu transformaciju tena 3-4 meseca dosledne upotrebe. Ključno je koristiti zaštitni faktor svakog dana.",
"en": "Due to its powerful effect, sea buckthorn oil requires patience. You'll see first changes in skin glow after 2-3 weeks. For visible lightening of dark spots, 6-8 weeks is needed, and for complete skin tone transformation, 3-4 months of consistent use. Daily sunscreen is crucial.",
"de": "Aufgrund seiner starken Wirkung erfordert Sanddornöl Geduld. Erste Veränderungen im Hautglanz sehen Sie nach 2-3 Wochen. Für eine sichtbare Aufhellung dunkler Flecken sind 6-8 Wochen erforderlich, und für eine komplette Teint-Transformation 3-4 Monate konsequenter Anwendung. Täglicher Sonnenschutz ist entscheidend.",
"fr": "En raison de son effet puissant, l'huile d'argousier demande de la patience. Vous verrez les premiers changements d'éclat de la peau après 2-3 semaines. Pour un éclaircissement visible des taches sombres, 6-8 semaines sont nécessaires, et pour une transformation complète du teint, 3-4 mois d'utilisation constante. Une protection solaire quotidienne est cruciale."
},
"timeframe": {
"sr": "2-3 nedelje za sjaj, 6-8 nedelja za tamne fleke, 3-4 meseca za transformaciju",
"en": "2-3 weeks for glow, 6-8 weeks for dark spots, 3-4 months for transformation",
"de": "2-3 Wochen für Glanz, 6-8 Wochen für dunkle Flecken, 3-4 Monate für Transformation",
"fr": "2-3 semaines pour l'éclat, 6-8 semaines pour les taches sombres, 3-4 mois pour la transformation"
},
"complementaryIngredients": [
"vitamin-c",
"niacinamide",
"licorice-root",
"kojic-acid"
],
"productsToShow": [
"Manoon Bright",
"Manoon 7"
],
"customerResults": [
{
"quote": {
"sr": "Posle godina borbe sa tamnim flekama od akni, konačno sam pronašla rešenje. Moja koža nikada nije izgledala bolje!",
"en": "After years of battling dark spots from acne, I finally found a solution. My skin has never looked better!",
"de": "Nach Jahren des Kampfes gegen dunkle Flecken von Akne habe ich endlich eine Lösung gefunden. Meine Haut hat noch nie besser ausgesehen!",
"fr": "Après des années de lutte contre les taches sombres dues à l'acné, j'ai finalement trouvé une solution. Ma peau n'a jamais été aussi belle!"
},
"name": "Sofija R.",
"age": 34,
"timeframe": "3 months"
}
],
"faqs": [
{
"question": {
"sr": "Da li ulje rakitovca boji kožu narandžasto?",
"en": "Does sea buckthorn oil stain skin orange?",
"de": "Färbt Sanddornöl die Haut orange?",
"fr": "L'huile d'argousier tache-t-elle la peau en orange?"
},
"answer": {
"sr": "Čisto ulje rakitovca ima intenzivnu narandžastu boju zbog beta-karotena. Preporučujemo mešanje sa nosiocem ulja (jojoba ili badem) u odnosu 1:1 ili 1:2 da biste izbegli privremeno bojenje kože.",
"en": "Pure sea buckthorn oil has an intense orange color due to beta-carotene. We recommend mixing with a carrier oil (jojoba or almond) in a 1:1 or 1:2 ratio to avoid temporary skin staining.",
"de": "Reines Sanddornöl hat aufgrund von Beta-Karotin eine intensive orangefarbene Farbe. Wir empfehlen, es mit Trägeröl (Jojoba oder Mandel) im Verhältnis 1:1 oder 1:2 zu mischen, um vorübergehende Hautfärbung zu vermeiden.",
"fr": "L'huile d'argousier pure a une couleur orange intense due au bêta-carotène. Nous recommandons de la mélanger avec une huile de support (jojoba ou amande) dans un ratio 1:1 ou 1:2 pour éviter la coloration temporaire de la peau."
}
},
{
"question": {
"sr": "Mogu li ga koristiti ujutru?",
"en": "Can I use it in the morning?",
"de": "Kann ich es morgens verwenden?",
"fr": "Puis-je l'utiliser le matin?"
},
"answer": {
"sr": "Ne preporučujemo jutarnju upotrebu jer visoka koncentracija vitamina C može učiniti kožu osetljivijom na sunce. Uvek koristite uveče i nanesite SPF 30+ ujutru.",
"en": "We don't recommend morning use as the high vitamin C concentration can make skin more sensitive to sun. Always use in the evening and apply SPF 30+ in the morning.",
"de": "Wir empfehlen keine morgendliche Anwendung, da die hohe Vitamin C-Konzentration die Haut sonnenempfindlicher machen kann. Verwenden Sie es immer abends und tragen Sie morgens LSF 30+ auf.",
"fr": "Nous ne recommandons pas l'utilisation le matin car la haute concentration en vitamine C peut rendre la peau plus sensible au soleil. Utilisez toujours le soir et appliquez SPF 30+ le matin."
}
}
],
"seoKeywords": {
"sr": {
"primary": ["ulje rakitovca za hiperpigmentaciju", "prirodno izbeljivanje kože", "ulje protiv tamnih fleka"],
"secondary": ["pege", "neujednačen ten", "prirodno posvetljavanje"],
"longTail": ["kako ukloniti tamne fleke", "najbolje ulje za pege", "prirodno rešenje za hiperpigmentaciju"]
},
"en": {
"primary": ["sea buckthorn oil hyperpigmentation", "natural skin brightening", "oil for dark spots"],
"secondary": ["freckles", "uneven skin tone", "natural lightening"],
"longTail": ["how to remove dark spots", "best oil for freckles", "natural hyperpigmentation solution"]
},
"de": {
"primary": ["Sanddornöl Hyperpigmentierung", "natürliche Hautaufhellung", "Öl für dunkle Flecken"],
"secondary": ["Sommersprossen", "unebener Teint", "natürliche Aufhellung"],
"longTail": ["dunkle Flecken entfernen", "bestes Öl für Sommersprossen", "natürliche Hyperpigmentierungslösung"]
},
"fr": {
"primary": ["huile d'argousier hyperpigmentation", "éclaircissement naturel", "huile pour taches sombres"],
"secondary": ["taches de rousseur", "teint inégal", "éclaircissement naturel"],
"longTail": ["comment enlever les taches sombres", "meilleure huile pour taches de rousseur", "solution naturelle hyperpigmentation"]
}
},
"relatedPages": {
"otherOilsForSameConcern": [
"best-licorice-oil-for-hyperpigmentation",
"best-vitamin-c-oil-for-hyperpigmentation"
],
"sameOilForOtherConcerns": [
"best-sea-buckthorn-oil-for-aging",
"best-sea-buckthorn-oil-for-dry-skin"
]
}
}

View File

@@ -0,0 +1,503 @@
# Programmatic SEO Plan for ManoonOils
## Executive Summary
Create 100+ SEO-optimized landing pages from structured datasets to capture high-intent search traffic and convert visitors into serum buyers.
---
## Dataset Ideas (7 Core Categories)
### 1. **Ingredient Benefits Database** ⭐ Highest Priority
**Dataset Size:** 50-100 ingredients × 4 locales = 200-400 pages
**Data Structure:**
```typescript
interface Ingredient {
slug: string; // "rosehip-oil"
name: {
sr: "Ulje divlje ruže";
en: "Rosehip Oil";
de: "Hagebuttenöl";
fr: "Huile de rose musquée";
};
benefits: string[]; // ["anti-aging", "hydration", "scars"]
skinTypes: string[]; // ["dry", "mature", "sensitive"]
scientificName: string;
origin: string;
extractionMethod: string;
keyCompounds: string[]; // ["vitamin A", "omega-3", "antioxidants"]
usageInstructions: string;
complementaryIngredients: string[]; // ["vitamin-e", "jojoba-oil"]
relatedProducts: string[]; // Product slugs to recommend
faqs: FAQ[];
seoKeywords: {
primary: string;
secondary: string[];
longTail: string[];
};
}
```
**Page Template:** `/ingredients/[slug]`
- Hero: Ingredient name + key benefit
- Scientific overview
- Benefits for skin (with icons)
- How to use (with video placeholder)
- "Best for" skin types
- Related Manoon products (product cards)
- FAQ schema markup
- CTA: "Shop serums with [ingredient]"
**Example Pages:**
- `/ingredients/rosehip-oil` - "Rosehip Oil for Anti-Aging: Benefits & How to Use"
- `/ingredients/bakuchiol` - "Bakuchiol: Natural Retinol Alternative"
- `/ingredients/sea-buckthorn` - "Sea Buckthorn Oil: Vitamin C Powerhouse"
---
### 2. **Skin Concern Solutions** ⭐ Highest Priority
**Dataset Size:** 20-30 concerns × 4 locales = 80-120 pages
**Data Structure:**
```typescript
interface SkinConcern {
slug: string; // "fine-lines"
name: {
sr: "Bore i linije";
en: "Fine Lines & Wrinkles";
de: "Feine Linien";
fr: "Rides et ridules";
};
description: string;
causes: string[];
bestIngredients: string[]; // Links to ingredient pages
recommendedRoutine: {
morning: string[];
evening: string[];
};
relatedProducts: string[];
beforeAfterImages: boolean;
testimonials: Testimonial[];
seoKeywords: SEOKeywords;
}
```
**Page Template:** `/concerns/[slug]`
- Empathy hook: "Struggling with [concern]?"
- Explain the problem
- Best ingredients (linking to ingredient pages)
- Recommended products
- Customer results/testimonials
- Free guide download (lead capture)
- CTA: "Start your transformation"
**Example Pages:**
- `/concerns/fine-lines` - "How to Reduce Fine Lines Naturally"
- `/concerns/hyperpigmentation` - "Dark Spots: Causes & Natural Solutions"
- `/concerns/dull-skin` - "Get Your Glow Back: Dull Skin Remedies"
---
### 3. **Ingredient Comparison Matrix**
**Dataset Size:** 50 ingredient pairs = 50 comparison pages
**Data Structure:**
```typescript
interface IngredientComparison {
slug: string; // "retinol-vs-bakuchiol"
ingredientA: string; // Reference to ingredient
ingredientB: string;
comparisonPoints: {
effectiveness: string;
gentleness: string;
price: string;
availability: string;
bestFor: string[];
};
winner: string | "tie";
recommendation: string; // "Choose X if..., Choose Y if..."
relatedProducts: {
a: string[];
b: string[];
};
}
```
**Page Template:** `/compare/[slug]`
- Head-to-head comparison table
- Which is better for what
- Product recommendations for both
- "Can't decide? Try our quiz"
- CTA: Shop both options
**Example Pages:**
- `/compare/retinol-vs-bakuchiol`
- `/compare/vitamin-c-vs-niacinamide`
- `/compare/rosehip-vs-argan-oil`
---
### 4. **Seasonal Skincare Guides**
**Dataset Size:** 4 seasons × 5 climates × 4 locales = 80 pages
**Data Structure:**
```typescript
interface SeasonalGuide {
slug: string; // "winter-skincare-routine"
season: "winter" | "spring" | "summer" | "autumn";
climate: "cold" | "dry" | "humid" | "temperate" | "tropical";
title: LocalizedString;
challenges: string[];
recommendedIngredients: string[];
routine: {
morning: RoutineStep[];
evening: RoutineStep[];
};
productBundle: string[];
tips: string[];
}
```
**Page Template:** `/guides/seasonal/[slug]`
- Season-specific challenges
- Ingredient recommendations
- Step-by-step routine
- Product bundle suggestion
- "Get the seasonal routine set" CTA
**Example Pages:**
- `/guides/seasonal/winter-skincare-routine`
- `/guides/seasonal/summer-anti-aging`
- `/guides/seasonal/spring-skin-renewal`
---
### 5. **Age-Specific Routines**
**Dataset Size:** 6 age groups × 4 locales = 24 pages
**Data Structure:**
```typescript
interface AgeRoutine {
slug: string; // "skincare-routine-30s"
ageRange: string; // "20s", "30s", "40s", "50s", "60s+"
title: LocalizedString;
skinChanges: string[];
keyConcerns: string[];
recommendedIngredients: string[];
routine: DailyRoutine;
productRecommendations: string[];
preventionTips: string[];
}
```
**Page Template:** `/routines/age/[slug]`
- "Best skincare routine for your [age]s"
- What happens to skin at this age
- Key ingredients to start using
- Morning & evening routine
- Product recommendations
- "Shop the [age]s routine bundle"
**Example Pages:**
- `/routines/age/skincare-routine-30s`
- `/routines/age/anti-aging-routine-40s`
- `/routines/age/mature-skin-care-50s`
---
### 6. **Skin Type Hubs**
**Dataset Size:** 6 skin types × 4 locales = 24 pages
**Data Structure:**
```typescript
interface SkinType {
slug: string; // "dry-skin"
name: LocalizedString;
characteristics: string[];
causes: string[];
ingredientsToLookFor: string[];
ingredientsToAvoid: string[];
recommendedProducts: string[];
routine: DailyRoutine;
tips: string[];
}
```
**Page Template:** `/skin-types/[slug]`
- Quiz: "Do you have [skin type]?"
- Characteristics checklist
- Best ingredients (with links)
- Complete routine
- Products specifically for this type
- CTA: "Build your [type] routine"
**Example Pages:**
- `/skin-types/dry-skin`
- `/skin-types/sensitive-skin`
- `/skin-types/combination-skin`
---
### 7. **Geographic/Climate-Specific**
**Dataset Size:** 20 regions × 4 seasons = 80 pages
**Data Structure:**
```typescript
interface ClimateGuide {
slug: string; // "skincare-for-cold-climates"
region: string;
climate: string;
challenges: string[];
recommendedIngredients: string[];
routineModifications: string;
productBundle: string[];
localTestimonials?: Testimonial[];
}
```
**Page Template:** `/climate/[slug]`
- "Skincare for [climate] climates"
- Local skin challenges
- Best ingredients for this climate
- Modified routine
- "Customers in [region] love..."
**Example Pages:**
- `/climate/skincare-for-cold-climates`
- `/climate/skincare-for-humid-climates`
- `/climate/skincare-for-arid-climates`
---
## Data Storage Strategy
### Option A: JSON Files (Recommended for MVP)
```
data/
├── ingredients/
│ ├── rosehip-oil.json
│ ├── bakuchiol.json
│ └── ...
├── concerns/
│ ├── fine-lines.json
│ ├── hyperpigmentation.json
│ └── ...
├── comparisons/
│ ├── retinol-vs-bakuchiol.json
│ └── ...
└── locales/
├── sr/
├── en/
├── de/
└── fr/
```
**Pros:**
- Easy to version control
- Simple to edit
- Fast to implement
- Works with Next.js static generation
### Option B: Headless CMS (Strapi/Sanity)
**Pros:**
- Non-technical team can edit
- Rich media support
- Relationships between entities
### Option C: Database (PostgreSQL/MongoDB)
**Pros:**
- Dynamic content
- User-generated content ready
- Advanced filtering
---
## Technical Implementation
### URL Structure
```
/ingredients/[slug] # Ingredient deep-dives
/concerns/[slug] # Problem-solving pages
/compare/[slug] # Comparison pages
/guides/seasonal/[slug] # Seasonal content
/routines/age/[slug] # Age-specific routines
/skin-types/[slug] # Skin type hubs
/climate/[slug] # Climate guides
```
### Page Generation (Next.js)
```typescript
// app/ingredients/[slug]/page.tsx
export async function generateStaticParams() {
const ingredients = await getAllIngredients();
return ingredients.map((i) => ({ slug: i.slug }));
}
export default async function IngredientPage({
params: { slug, locale }
}) {
const ingredient = await getIngredient(slug, locale);
return <IngredientTemplate data={ingredient} />;
}
```
### SEO Template Fields (Per Page)
```typescript
interface SEOTemplate {
title: string; // "Rosehip Oil for Anti-Aging | Benefits & Uses | ManoonOils"
metaDescription: string; // 155 chars with keywords
canonical: string; // Full URL
ogTitle: string;
ogDescription: string;
ogImage: string; // Dynamic OG image with ingredient
keywords: string[];
faqSchema: FAQPageSchema;
productSchema?: ProductSchema;
breadcrumb: BreadcrumbItem[];
}
```
---
## Content Templates
### Ingredient Page Template
```
H1: [Ingredient Name] for [Primary Benefit]: Complete Guide
Hero Section:
- Large ingredient image
- Key benefits (3 icons)
- CTA: "Shop [ingredient] serums"
H2: What is [Ingredient]?
- Scientific explanation
- Origin & extraction
- Key compounds
H2: Benefits of [Ingredient] for Skin
- H3: Anti-aging properties
- H3: Hydration benefits
- H3: Additional benefits
H2: Best Skin Types for [Ingredient]
- Visual skin type selector
H2: How to Use [Ingredient] in Your Routine
- Morning routine
- Evening routine
- What to pair with (links to comparisons)
H2: Our [Ingredient] Products
- Product cards with prices
- "Shop all [ingredient] products"
H2: Frequently Asked Questions
- FAQ schema markup
- 5-7 common questions
Related Content:
- Compare with similar ingredients
- Read about skin concerns it treats
CTA: "Start your [ingredient] routine"
```
---
## Conversion Strategy
### Lead Magnets (Email Capture)
1. **"The Natural Anti-Aging Guide"** - PDF download
2. **"Ingredient Compatibility Chart"** - Interactive tool
3. **"Personalized Routine Quiz"** - Email results
4. **"Seasonal Skincare Calendar"** - Year-long guide
### Product CTAs
1. **Primary:** "Shop [ingredient] serums" → Category page
2. **Secondary:** "Get the complete routine" → Bundle offer
3. **Tertiary:** "Take the skin quiz" → Lead capture
### Cross-Selling
- "Customers who viewed [ingredient] also bought..."
- "Complete your routine with..."
- "Pair with [complementary ingredient] for best results"
---
## Expected Traffic & ROI
### Traffic Estimates (6-month projection)
| Dataset | Pages | Avg Monthly Searches/Page | Est. Monthly Traffic |
|---------|-------|---------------------------|---------------------|
| Ingredients | 100 | 500 | 5,000 |
| Concerns | 50 | 1,000 | 10,000 |
| Comparisons | 50 | 800 | 8,000 |
| Seasonal | 80 | 300 | 6,000 |
| Age Routines | 24 | 600 | 3,000 |
| Skin Types | 24 | 700 | 3,000 |
| Climate | 80 | 200 | 2,000 |
| **TOTAL** | **408** | **-** | **37,000** |
### Conversion Targets
- **Organic CTR:** 3-5% (industry average)
- **Page-to-Product CTR:** 15-20%
- **Product-to-Purchase:** 2-3%
- **Estimated Monthly Revenue:** €15,000-30,000 (at €50 AOV)
---
## Implementation Timeline
### Phase 1: Foundation (Weeks 1-2)
- [ ] Set up data structure
- [ ] Create 10 priority ingredient pages
- [ ] Build reusable templates
- [ ] Implement JSON-LD schemas
### Phase 2: Core Content (Weeks 3-6)
- [ ] Create 50 ingredient pages
- [ ] Create 20 concern pages
- [ ] Build comparison tool
- [ ] Add lead magnets
### Phase 3: Scale (Weeks 7-10)
- [ ] Generate all 400+ pages
- [ ] Implement internal linking
- [ ] Add dynamic OG images
- [ ] A/B test CTAs
### Phase 4: Optimize (Weeks 11-12)
- [ ] Analyze top performers
- [ ] Update underperformers
- [ ] Add user-generated content
- [ ] Expand winning categories
---
## Success Metrics
### SEO Metrics
- **Organic traffic:** 37,000+/month by month 6
- **Keyword rankings:** Top 10 for 100+ keywords
- **Featured snippets:** Capture 20+ position 0
- **Domain authority:** Increase from current baseline
### Business Metrics
- **Revenue from organic:** €15,000-30,000/month
- **Email list growth:** 1,000+ subscribers/month
- **Customer acquisition cost:** Lower than paid ads
- **Lifetime value:** Higher (organic customers retain better)
---
## Next Steps
1. **Approve dataset priorities** - Which categories to start with?
2. **Create data structure** - Set up JSON/CMS schemas
3. **Build 3 sample pages** - One from each priority category
4. **Test & iterate** - Measure performance before scaling
5. **Full production** - Generate all 400+ pages
Want me to start building the data structure and first sample pages?

View File

@@ -0,0 +1,106 @@
import {
getOilForConcernPage,
getAllSolutionSlugs,
getLocalizedString,
getLocalizedKeywords
} from "@/lib/programmatic-seo/dataLoader";
import { getProducts } from "@/lib/saleor";
import { OilForConcernPageTemplate } from "@/components/programmatic-seo/OilForConcernPage";
import { FAQSchema } from "@/components/programmatic-seo/FAQSchema";
import { isValidLocale, DEFAULT_LOCALE, type Locale } from "@/lib/i18n/locales";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
interface PageProps {
params: Promise<{ locale: string; slug: string }>;
}
export async function generateStaticParams() {
return await getAllSolutionSlugs();
}
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://manoonoils.com";
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { locale, slug } = await params;
const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
const page = await getOilForConcernPage(slug);
if (!page) {
return {
title: "Page Not Found",
};
}
const metaTitle = getLocalizedString(page.metaTitle, validLocale);
const metaDescription = getLocalizedString(page.metaDescription, validLocale);
const keywords = getLocalizedKeywords(page.seoKeywords, validLocale);
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/solutions/${page.slug}`;
return {
title: metaTitle,
description: metaDescription,
keywords: keywords.join(", "),
alternates: {
canonical: canonicalUrl,
languages: {
"sr": `${baseUrl}/solutions/${page.slug}`,
"en": `${baseUrl}/en/solutions/${page.slug}`,
"de": `${baseUrl}/de/solutions/${page.slug}`,
"fr": `${baseUrl}/fr/solutions/${page.slug}`,
},
},
openGraph: {
title: metaTitle,
description: metaDescription,
type: "article",
url: canonicalUrl,
images: [{
url: `${baseUrl}/og-image.jpg`,
width: 1200,
height: 630,
alt: metaTitle,
}],
locale: validLocale,
},
twitter: {
card: "summary_large_image",
title: metaTitle,
description: metaDescription,
images: [`${baseUrl}/og-image.jpg`],
},
};
}
export default async function SolutionPage({ params }: PageProps) {
const { locale, slug } = await params;
const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
const [page, products] = await Promise.all([
getOilForConcernPage(slug),
getProducts(validLocale === "sr" ? "SR" : "EN", 4)
]);
if (!page) {
notFound();
}
const basePath = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const faqQuestions = page.faqs.map((faq) => ({
question: getLocalizedString(faq.question, validLocale),
answer: getLocalizedString(faq.answer, validLocale),
}));
return (
<>
<FAQSchema questions={faqQuestions} />
<OilForConcernPageTemplate
page={page}
locale={validLocale as Locale}
basePath={basePath}
products={products}
/>
</>
);
}

View File

@@ -0,0 +1,154 @@
import { Metadata } from "next";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { ChevronRight, Search } from "lucide-react";
import { getAllOilForConcernPages, getLocalizedString } from "@/lib/programmatic-seo/dataLoader";
type Params = Promise<{ locale: string }>;
export async function generateMetadata({
params,
}: {
params: Params;
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions.ByConcern" });
return {
title: t("metaTitle"),
description: t("metaDescription"),
};
}
function groupByConcern(pages: Awaited<ReturnType<typeof getAllOilForConcernPages>>) {
const concerns = new Map<string, typeof pages>();
pages.forEach((page) => {
const concernSlug = page.concernSlug;
if (!concerns.has(concernSlug)) {
concerns.set(concernSlug, []);
}
concerns.get(concernSlug)?.push(page);
});
return concerns;
}
interface ConcernCardProps {
concernSlug: string;
concernName: string;
oilCount: number;
topOils: string[];
locale: string;
}
function ConcernCard({ concernSlug, concernName, oilCount, topOils, locale }: ConcernCardProps) {
return (
<div className="border border-[#e5e5e5] rounded-lg p-6 hover:border-black transition-colors group">
<h3 className="text-lg font-medium text-[#1a1a1a] mb-2">
{concernName}
</h3>
<p className="text-sm text-[#666666] mb-4">
{oilCount} {oilCount === 1 ? "oil solution" : "oil solutions"} available
</p>
<div className="space-y-2 mb-4">
{topOils.slice(0, 3).map((oilName) => (
<div key={oilName} className="flex items-center gap-2 text-sm text-[#666666]">
<div className="w-1.5 h-1.5 rounded-full bg-amber-400" />
{oilName}
</div>
))}
</div>
<Link
href={`/${locale}/solutions/by-concern/${concernSlug}`}
className="inline-flex items-center text-sm font-medium text-[#1a1a1a] group-hover:text-black transition-colors"
>
View All Solutions
<ChevronRight className="ml-1 w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
</Link>
</div>
);
}
export default async function ByConcernPage({
params,
}: {
params: Params;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions" });
const pageT = await getTranslations({ locale, namespace: "Solutions.ByConcern" });
const pages = await getAllOilForConcernPages();
const concernsMap = groupByConcern(pages);
const concernsList = Array.from(concernsMap.entries())
.map(([slug, pages]) => ({
slug,
name: getLocalizedString(pages[0].concernName, locale),
oilCount: pages.length,
topOils: pages.slice(0, 3).map((p) => getLocalizedString(p.oilName, locale)),
}))
.sort((a, b) => a.name.localeCompare(b.name));
return (
<div className="min-h-screen bg-white">
<section className="pt-32 pb-16 lg:pt-40 lg:pb-24">
<div className="container">
<nav className="flex items-center gap-2 text-sm text-[#666666] mb-8">
<Link href={`/${locale}`} className="hover:text-black transition-colors">
{t("breadcrumb.home")}
</Link>
<ChevronRight className="w-4 h-4" />
<Link href={`/${locale}/solutions`} className="hover:text-black transition-colors">
{t("breadcrumb.solutions")}
</Link>
<ChevronRight className="w-4 h-4" />
<span className="text-[#1a1a1a]">{t("breadcrumb.byConcern")}</span>
</nav>
<div className="max-w-3xl mb-12">
<h1 className="text-4xl lg:text-5xl font-medium tracking-tight text-[#1a1a1a] mb-6">
{pageT("title")}
</h1>
<p className="text-lg text-[#666666] leading-relaxed">
{pageT("subtitle")}
</p>
</div>
<div className="bg-[#fafafa] border border-[#e5e5e5] rounded-lg p-6 mb-12">
<div className="flex items-center gap-3 text-[#666666]">
<Search className="w-5 h-5" />
<span className="text-sm">
{pageT("stats.availableConcerns", { count: concernsList.length })}
</span>
<span className="text-[#e5e5e5]">|</span>
<span className="text-sm">
{pageT("stats.totalSolutions", { count: pages.length })}
</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{concernsList.map((concern) => (
<ConcernCard
key={concern.slug}
concernSlug={concern.slug}
concernName={concern.name}
oilCount={concern.oilCount}
topOils={concern.topOils}
locale={locale}
/>
))}
</div>
{concernsList.length === 0 && (
<div className="text-center py-16">
<p className="text-[#666666]">{pageT("noResults")}</p>
</div>
)}
</div>
</section>
</div>
);
}

View File

@@ -0,0 +1,165 @@
import { Metadata } from "next";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { ChevronRight, Droplets } from "lucide-react";
import { getAllOilForConcernPages, getLocalizedString } from "@/lib/programmatic-seo/dataLoader";
type Params = Promise<{ locale: string }>;
export async function generateMetadata({
params,
}: {
params: Params;
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions.ByOil" });
return {
title: t("metaTitle"),
description: t("metaDescription"),
};
}
function groupByOil(pages: Awaited<ReturnType<typeof getAllOilForConcernPages>>) {
const oils = new Map<string, typeof pages>();
pages.forEach((page) => {
const oilSlug = page.oilSlug;
if (!oils.has(oilSlug)) {
oils.set(oilSlug, []);
}
oils.get(oilSlug)?.push(page);
});
return oils;
}
interface OilCardProps {
oilSlug: string;
oilName: string;
concernCount: number;
topConcerns: string[];
locale: string;
}
function OilCard({ oilSlug, oilName, concernCount, topConcerns, locale }: OilCardProps) {
return (
<div className="border border-[#e5e5e5] rounded-lg p-6 hover:border-black transition-colors group">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-full bg-amber-100 flex items-center justify-center">
<Droplets className="w-5 h-5 text-amber-700" />
</div>
<h3 className="text-lg font-medium text-[#1a1a1a]">
{oilName}
</h3>
</div>
<p className="text-sm text-[#666666] mb-4">
{concernCount} {concernCount === 1 ? "concern solution" : "concern solutions"} available
</p>
<div className="space-y-2 mb-4">
<p className="text-xs uppercase tracking-wider text-[#999999] font-medium">
Best for:
</p>
{topConcerns.slice(0, 3).map((concernName) => (
<div key={concernName} className="flex items-center gap-2 text-sm text-[#666666]">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
{concernName}
</div>
))}
</div>
<Link
href={`/${locale}/solutions/by-oil/${oilSlug}`}
className="inline-flex items-center text-sm font-medium text-[#1a1a1a] group-hover:text-black transition-colors"
>
Explore Oil Solutions
<ChevronRight className="ml-1 w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
</Link>
</div>
);
}
export default async function ByOilPage({
params,
}: {
params: Params;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions" });
const pageT = await getTranslations({ locale, namespace: "Solutions.ByOil" });
const pages = await getAllOilForConcernPages();
const oilsMap = groupByOil(pages);
const oilsList = Array.from(oilsMap.entries())
.map(([slug, pages]) => ({
slug,
name: getLocalizedString(pages[0].oilName, locale),
concernCount: pages.length,
topConcerns: pages.slice(0, 3).map((p) => getLocalizedString(p.concernName, locale)),
}))
.sort((a, b) => a.name.localeCompare(b.name));
return (
<div className="min-h-screen bg-white">
<section className="pt-32 pb-16 lg:pt-40 lg:pb-24">
<div className="container">
<nav className="flex items-center gap-2 text-sm text-[#666666] mb-8">
<Link href={`/${locale}`} className="hover:text-black transition-colors">
{t("breadcrumb.home")}
</Link>
<ChevronRight className="w-4 h-4" />
<Link href={`/${locale}/solutions`} className="hover:text-black transition-colors">
{t("breadcrumb.solutions")}
</Link>
<ChevronRight className="w-4 h-4" />
<span className="text-[#1a1a1a]">{t("breadcrumb.byOil")}</span>
</nav>
<div className="max-w-3xl mb-12">
<h1 className="text-4xl lg:text-5xl font-medium tracking-tight text-[#1a1a1a] mb-6">
{pageT("title")}
</h1>
<p className="text-lg text-[#666666] leading-relaxed">
{pageT("subtitle")}
</p>
</div>
<div className="bg-gradient-to-r from-amber-50 to-emerald-50 border border-[#e5e5e5] rounded-lg p-6 mb-12">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-white flex items-center justify-center shadow-sm">
<Droplets className="w-6 h-6 text-amber-600" />
</div>
<div>
<p className="text-sm text-[#666666]">
{pageT("stats.availableOils", { count: oilsList.length })}
</p>
<p className="text-sm text-[#666666]">
{pageT("stats.totalSolutions", { count: pages.length })}
</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{oilsList.map((oil) => (
<OilCard
key={oil.slug}
oilSlug={oil.slug}
oilName={oil.name}
concernCount={oil.concernCount}
topConcerns={oil.topConcerns}
locale={locale}
/>
))}
</div>
{oilsList.length === 0 && (
<div className="text-center py-16">
<p className="text-[#666666]">{pageT("noResults")}</p>
</div>
)}
</div>
</section>
</div>
);
}

View File

@@ -0,0 +1,291 @@
import { Metadata } from "next";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { ChevronRight, Sparkles, Heart, Leaf, Sun, Moon, Clock, Globe, Users, Droplets, ArrowRight } from "lucide-react";
type Params = Promise<{ locale: string }>;
export async function generateMetadata({
params,
}: {
params: Params;
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions.Hub" });
return {
title: t("metaTitle"),
description: t("metaDescription"),
};
}
interface CategoryCardProps {
title: string;
description: string;
href: string;
icon: React.ReactNode;
priority?: boolean;
}
function CategoryCard({ title, description, href, icon, priority }: CategoryCardProps) {
return (
<Link
href={href}
className={`group block p-6 lg:p-8 border border-[#e5e5e5] rounded-lg hover:border-black transition-all duration-300 hover:shadow-lg ${
priority ? "bg-gradient-to-br from-amber-50/50 to-white" : "bg-white"
}`}
>
<div className="flex items-start gap-4">
<div className={`flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center ${
priority ? "bg-amber-100 text-amber-700" : "bg-[#f5f5f5] text-[#666666] group-hover:bg-black group-hover:text-white"
} transition-colors`}>
{icon}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h3 className="text-lg font-medium text-[#1a1a1a] group-hover:text-black transition-colors">
{title}
</h3>
{priority && (
<span className="px-2 py-0.5 text-[10px] uppercase tracking-wider font-medium bg-amber-100 text-amber-700 rounded-full">
Popular
</span>
)}
</div>
<p className="text-sm text-[#666666] leading-relaxed mb-4">
{description}
</p>
<span className="inline-flex items-center text-sm font-medium text-[#1a1a1a] group-hover:text-black transition-colors">
{priority ? "Explore Solutions" : "Learn More"}
<ArrowRight className="ml-1 w-4 h-4 transform group-hover:translate-x-1 transition-transform" />
</span>
</div>
</div>
</Link>
);
}
interface QuickLinkProps {
title: string;
href: string;
count?: number;
}
function QuickLink({ title, href, count }: QuickLinkProps) {
return (
<Link
href={href}
className="flex items-center justify-between p-4 border-b border-[#e5e5e5] hover:bg-[#fafafa] transition-colors group"
>
<span className="text-[#1a1a1a] group-hover:text-black transition-colors">
{title}
</span>
<div className="flex items-center gap-2">
{count !== undefined && (
<span className="text-xs text-[#999999]">{count} solutions</span>
)}
<ChevronRight className="w-4 h-4 text-[#999999] group-hover:text-black transition-colors" />
</div>
</Link>
);
}
export default async function SolutionsHubPage({
params,
}: {
params: Params;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Solutions" });
const hubT = await getTranslations({ locale, namespace: "Solutions.Hub" });
const categories = [
{
title: hubT("categories.oilForConcern.title"),
description: hubT("categories.oilForConcern.description"),
href: `/${locale}/solutions/by-concern`,
icon: <Droplets className="w-5 h-5" />,
priority: true,
},
{
title: hubT("categories.ageSkinRoutine.title"),
description: hubT("categories.ageSkinRoutine.description"),
href: `/${locale}/solutions/age-skin-routine`,
icon: <Clock className="w-5 h-5" />,
},
{
title: hubT("categories.ingredientPairings.title"),
description: hubT("categories.ingredientPairings.description"),
href: `/${locale}/solutions/ingredient-pairings`,
icon: <Sparkles className="w-5 h-5" />,
},
{
title: hubT("categories.bodyPartConcerns.title"),
description: hubT("categories.bodyPartConcerns.description"),
href: `/${locale}/solutions/body-part-concerns`,
icon: <Heart className="w-5 h-5" />,
},
{
title: hubT("categories.oilComparisons.title"),
description: hubT("categories.oilComparisons.description"),
href: `/${locale}/solutions/oil-comparisons`,
icon: <Users className="w-5 h-5" />,
},
{
title: hubT("categories.routineStepSkinType.title"),
description: hubT("categories.routineStepSkinType.description"),
href: `/${locale}/solutions/routine-step-skin-type`,
icon: <Leaf className="w-5 h-5" />,
},
{
title: hubT("categories.seasonalSkincare.title"),
description: hubT("categories.seasonalSkincare.description"),
href: `/${locale}/solutions/seasonal-skincare`,
icon: <Sun className="w-5 h-5" />,
},
{
title: hubT("categories.timeOfDayConcerns.title"),
description: hubT("categories.timeOfDayConcerns.description"),
href: `/${locale}/solutions/time-of-day-concerns`,
icon: <Moon className="w-5 h-5" />,
},
{
title: hubT("categories.naturalAlternatives.title"),
description: hubT("categories.naturalAlternatives.description"),
href: `/${locale}/solutions/natural-alternatives`,
icon: <Leaf className="w-5 h-5" />,
},
{
title: hubT("categories.culturalBeautySecrets.title"),
description: hubT("categories.culturalBeautySecrets.description"),
href: `/${locale}/solutions/cultural-beauty-secrets`,
icon: <Globe className="w-5 h-5" />,
},
];
return (
<div className="min-h-screen bg-white">
<section className="pt-32 pb-16 lg:pt-40 lg:pb-24">
<div className="container">
<nav className="flex items-center gap-2 text-sm text-[#666666] mb-8">
<Link href={`/${locale}`} className="hover:text-black transition-colors">
{t("breadcrumb.home")}
</Link>
<ChevronRight className="w-4 h-4" />
<span className="text-[#1a1a1a]">{t("breadcrumb.solutions")}</span>
</nav>
<div className="max-w-3xl">
<h1 className="text-4xl lg:text-5xl font-medium tracking-tight text-[#1a1a1a] mb-6">
{hubT("title")}
</h1>
<p className="text-lg text-[#666666] leading-relaxed">
{hubT("subtitle")}
</p>
</div>
</div>
</section>
<section className="pb-16 lg:pb-24">
<div className="container">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 lg:gap-6">
{categories.map((category) => (
<CategoryCard key={category.href} {...category} />
))}
</div>
</div>
</section>
<section className="pb-16 lg:pb-24">
<div className="container">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
<div className="border border-[#e5e5e5] rounded-lg overflow-hidden">
<div className="p-6 bg-[#fafafa] border-b border-[#e5e5e5]">
<h2 className="text-lg font-medium text-[#1a1a1a]">
{hubT("quickAccess.byConcern")}
</h2>
<p className="text-sm text-[#666666] mt-1">
{hubT("quickAccess.byConcernDesc")}
</p>
</div>
<div className="divide-y divide-[#e5e5e5]">
<QuickLink
title={hubT("quickAccess.links.wrinkles")}
href={`/${locale}/solutions/by-concern/wrinkles`}
/>
<QuickLink
title={hubT("quickAccess.links.acne")}
href={`/${locale}/solutions/by-concern/acne`}
/>
<QuickLink
title={hubT("quickAccess.links.drySkin")}
href={`/${locale}/solutions/by-concern/dry-skin`}
/>
<QuickLink
title={hubT("quickAccess.links.darkSpots")}
href={`/${locale}/solutions/by-concern/dark-spots`}
/>
<QuickLink
title={hubT("quickAccess.links.viewAll")}
href={`/${locale}/solutions/by-concern`}
/>
</div>
</div>
<div className="border border-[#e5e5e5] rounded-lg overflow-hidden">
<div className="p-6 bg-[#fafafa] border-b border-[#e5e5e5]">
<h2 className="text-lg font-medium text-[#1a1a1a]">
{hubT("quickAccess.byOil")}
</h2>
<p className="text-sm text-[#666666] mt-1">
{hubT("quickAccess.byOilDesc")}
</p>
</div>
<div className="divide-y divide-[#e5e5e5]">
<QuickLink
title={hubT("quickAccess.links.rosehipOil")}
href={`/${locale}/solutions/by-oil/rosehip-oil`}
/>
<QuickLink
title={hubT("quickAccess.links.arganOil")}
href={`/${locale}/solutions/by-oil/argan-oil`}
/>
<QuickLink
title={hubT("quickAccess.links.jojobaOil")}
href={`/${locale}/solutions/by-oil/jojoba-oil`}
/>
<QuickLink
title={hubT("quickAccess.links.seaBuckthornOil")}
href={`/${locale}/solutions/by-oil/sea-buckthorn-oil`}
/>
<QuickLink
title={hubT("quickAccess.links.viewAll")}
href={`/${locale}/solutions/by-oil`}
/>
</div>
</div>
</div>
</div>
</section>
<section className="pb-16 lg:pb-24">
<div className="container">
<div className="bg-[#1a1a1a] rounded-2xl p-8 lg:p-12 text-center">
<h2 className="text-2xl lg:text-3xl font-medium text-white mb-4">
{hubT("cta.title")}
</h2>
<p className="text-[#999999] max-w-xl mx-auto mb-8">
{hubT("cta.description")}
</p>
<Link
href={`/${locale}/products`}
className="inline-flex items-center justify-center px-8 py-3 bg-white text-[#1a1a1a] font-medium rounded-full hover:bg-[#f5f5f5] transition-colors"
>
{hubT("cta.button")}
</Link>
</div>
</div>
</section>
</div>
);
}

View File

@@ -1,5 +1,6 @@
import { MetadataRoute } from "next"; import { MetadataRoute } from "next";
import { getProducts, filterOutBundles } from "@/lib/saleor"; import { getProducts, filterOutBundles } from "@/lib/saleor";
import { getAllOilForConcernPages } from "@/lib/programmatic-seo/dataLoader";
import { SUPPORTED_LOCALES, type Locale } from "@/lib/i18n/locales"; import { SUPPORTED_LOCALES, type Locale } from "@/lib/i18n/locales";
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://manoonoils.com"; const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://manoonoils.com";
@@ -105,5 +106,35 @@ export default async function sitemap(): Promise<SitemapEntry[]> {
} }
} }
return [...staticPages, ...productUrls]; let solutionPages: any[] = [];
try {
solutionPages = await getAllOilForConcernPages();
} catch (e) {
console.log("Failed to fetch solution pages for sitemap during build");
}
const solutionUrls: SitemapEntry[] = [];
for (const page of solutionPages) {
const hreflangs: Record<string, string> = {};
for (const locale of SUPPORTED_LOCALES) {
const path = locale === "sr" ? `/solutions/${page.slug}` : `/${locale}/solutions/${page.slug}`;
hreflangs[locale] = `${baseUrl}${path}`;
}
for (const locale of SUPPORTED_LOCALES) {
const localePrefix = locale === "sr" ? "" : `/${locale}`;
solutionUrls.push({
url: `${baseUrl}${localePrefix}/solutions/${page.slug}`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.7,
alternates: {
languages: hreflangs,
},
});
}
}
return [...staticPages, ...productUrls, ...solutionUrls];
} }

View File

@@ -32,12 +32,18 @@ export default function ExitIntentDetector() {
try { try {
const response = await fetch("/api/geoip"); const response = await fetch("/api/geoip");
if (response.ok) { if (response.ok) {
const data = await response.json(); const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const text = await response.text();
if (text && text.trim()) {
const data = JSON.parse(text);
setCountry(data.country); setCountry(data.country);
setCountryCode(data.countryCode); setCountryCode(data.countryCode);
setCity(data.city || ""); setCity(data.city || "");
setRegion(data.region || ""); setRegion(data.region || "");
} }
}
}
} catch (error) { } catch (error) {
console.error("Failed to get country:", error); console.error("Failed to get country:", error);
} }

View File

@@ -21,6 +21,12 @@ export default function Footer({ locale = "sr" }: FooterProps) {
{ label: t("skinCare"), href: `${localePath}/products` }, { label: t("skinCare"), href: `${localePath}/products` },
{ label: t("giftSets"), href: `${localePath}/products` }, { label: t("giftSets"), href: `${localePath}/products` },
], ],
solutions: [
{ label: t("allSolutions"), href: `${localePath}/solutions` },
{ label: t("byConcern"), href: `${localePath}/solutions/by-concern` },
{ label: t("byOil"), href: `${localePath}/solutions/by-oil` },
{ label: t("skincareGuide"), href: `${localePath}/solutions` },
],
about: [ about: [
{ label: t("ourStory"), href: `${localePath}/about` }, { label: t("ourStory"), href: `${localePath}/about` },
{ label: t("process"), href: `${localePath}/about` }, { label: t("process"), href: `${localePath}/about` },
@@ -74,7 +80,7 @@ export default function Footer({ locale = "sr" }: FooterProps) {
</div> </div>
<div className="lg:col-span-8"> <div className="lg:col-span-8">
<div className="grid grid-cols-2 md:grid-cols-3 gap-8"> <div className="grid grid-cols-2 md:grid-cols-4 gap-8">
<div className="flex flex-col"> <div className="flex flex-col">
<h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]"> <h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]">
{t("shop")} {t("shop")}
@@ -93,6 +99,24 @@ export default function Footer({ locale = "sr" }: FooterProps) {
</ul> </ul>
</div> </div>
<div className="flex flex-col">
<h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]">
{t("solutions")}
</h4>
<ul className="space-y-3">
{footerLinks.solutions.map((link) => (
<li key={link.label}>
<Link
href={link.href}
className="text-sm text-[#666666] hover:text-black transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
<div className="flex flex-col"> <div className="flex flex-col">
<h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]"> <h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]">
{t("about")} {t("about")}

View File

@@ -0,0 +1,16 @@
import { generateFAQPageSchema } from "@/lib/programmatic-seo/schema";
interface FAQSchemaProps {
questions: Array<{ question: string; answer: string }>;
}
export function FAQSchema({ questions }: FAQSchemaProps) {
const schema = generateFAQPageSchema(questions);
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}

View File

@@ -0,0 +1,157 @@
import type { Locale } from "@/lib/i18n/locales";
import type { OilForConcernPage } from "@/lib/programmatic-seo/types";
import type { Product } from "@/types/saleor";
import { getLocalizedString, getLocalizedArray } from "@/lib/programmatic-seo/dataLoader";
import Header from "@/components/layout/Header";
import Footer from "@/components/layout/Footer";
import ProductReviews from "@/components/product/ProductReviews";
import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
import ProductsGrid from "./ProductsGrid";
import Link from "next/link";
import { ArrowRight, Check, Clock, Droplets } from "lucide-react";
interface OilForConcernPageProps {
page: OilForConcernPage;
locale: Locale;
basePath: string;
products: Product[];
}
export function OilForConcernPageTemplate({ page, locale, basePath, products }: OilForConcernPageProps) {
const pageTitle = getLocalizedString(page.pageTitle, locale);
const oilName = getLocalizedString(page.oilName, locale);
const concernName = getLocalizedString(page.concernName, locale);
const whyThisWorks = getLocalizedString(page.whyThisWorks, locale);
const keyBenefits = getLocalizedArray(page.keyBenefits, locale);
const howToApply = getLocalizedArray(page.howToApply, locale);
const expectedResults = getLocalizedString(page.expectedResults, locale);
const timeframe = getLocalizedString(page.timeframe, locale);
const productsHref = locale === "sr" ? "/products" : `/${locale}/products`;
return (
<>
<Header locale={locale} />
<main className="min-h-screen bg-white">
<section className="bg-[#FAF9F7] pt-[180px] lg:pt-[200px] pb-16">
<div className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-4xl mx-auto text-center">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-[#E8DFD0] rounded-full mb-6">
<Droplets className="w-4 h-4 text-[#8B7355]" />
<span className="text-sm text-[#5C4D3C] font-medium">
{locale === "sr" ? "Prirodno rešenje" :
locale === "de" ? "Natürliche Lösung" :
locale === "fr" ? "Solution naturelle" : "Natural Solution"}
</span>
</div>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-light text-[#1A1A1A] mb-6 leading-tight">
{pageTitle}
</h1>
<p className="text-lg text-[#666666] max-w-2xl mx-auto mb-8">
{whyThisWorks.substring(0, 150)}...
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href={productsHref}
className="inline-flex items-center justify-center gap-2 px-8 py-4 bg-[#1A1A1A] text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
>
{locale === "sr" ? "Kupi proizvode sa " :
locale === "de" ? "Produkte mit " :
locale === "fr" ? "Acheter des produits avec " : "Shop products with "}
{oilName}
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
</div>
</section>
<ProductReviews locale={locale} productName={oilName} />
<section className="py-16 lg:py-24">
<div className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid lg:grid-cols-2 gap-12 items-center">
<div>
<h2 className="text-3xl md:text-4xl font-light text-[#1A1A1A] mb-6">
{locale === "sr" ? "Zašto " :
locale === "de" ? "Warum " :
locale === "fr" ? "Pourquoi " : "Why "}
{oilName}
{locale === "sr" ? " deluje protiv " :
locale === "de" ? " gegen " :
locale === "fr" ? " contre " : " works for "}
{concernName.toLowerCase()}
</h2>
<p className="text-[#666666] text-lg leading-relaxed mb-8">
{whyThisWorks}
</p>
<div className="flex items-center gap-3 p-4 bg-[#F8F7F5] rounded-lg">
<Clock className="w-5 h-5 text-[#8B7355]" />
<div>
<span className="text-sm font-medium text-[#1A1A1A]">
{locale === "sr" ? "Vreme rezultata: " :
locale === "de" ? "Zeit bis zum Ergebnis: " :
locale === "fr" ? "Délai des résultats: " : "Results timeframe: "}
</span>
<span className="text-sm text-[#666666]">{timeframe}</span>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
{keyBenefits.slice(0, 4).map((benefit, index) => (
<div key={index} className="p-6 bg-[#FAF9F7] rounded-lg">
<Check className="w-6 h-6 text-[#8B7355] mb-3" />
<p className="text-[#1A1A1A] font-medium">{benefit}</p>
</div>
))}
</div>
</div>
</div>
</section>
<BeforeAfterGallery />
<section className="py-16 lg:py-24">
<div className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl md:text-4xl font-light text-[#1A1A1A] text-center mb-12">
{locale === "sr" ? "Kako koristiti" :
locale === "de" ? "Anwendung" :
locale === "fr" ? "Comment utiliser" : "How to use"}
</h2>
<div className="max-w-3xl mx-auto">
<div className="space-y-6">
{howToApply.map((step, index) => (
<div key={index} className="flex gap-4 items-start">
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-[#1A1A1A] text-white flex items-center justify-center font-medium">
{index + 1}
</div>
<div className="pt-2">
<p className="text-[#1A1A1A] text-lg">{step}</p>
</div>
</div>
))}
</div>
</div>
</div>
</section>
<section className="py-16 lg:py-24 bg-[#FAF9F7]">
<div className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-3xl md:text-4xl font-light text-[#1A1A1A] mb-6">
{locale === "sr" ? "Šta možete očekivati" :
locale === "de" ? "Was Sie erwarten können" :
locale === "fr" ? "Ce que vous pouvez attendre" : "What to expect"}
</h2>
<p className="text-lg text-[#666666] leading-relaxed">
{expectedResults}
</p>
</div>
</div>
</section>
<ProductsGrid products={products} locale={locale} />
</main>
<Footer locale={locale} />
</>
);
}

View File

@@ -0,0 +1,159 @@
"use client";
import { motion } from "framer-motion";
import Image from "next/image";
import { useState } from "react";
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
import { useAnalytics } from "@/lib/analytics";
import type { Product } from "@/types/saleor";
import { getProductPrice, getProductImage, getLocalizedProduct } from "@/lib/saleor";
import { isValidLocale, getSaleorLocale } from "@/lib/i18n/locales";
import { useTranslations } from "next-intl";
import Link from "next/link";
interface ProductsGridProps {
products: Product[];
locale: string;
}
function ProductCardWithAddToCart({ product, index, locale }: { product: Product; index: number; locale: string }) {
const t = useTranslations("ProductCard");
const tProduct = useTranslations("Product");
const [isAdding, setIsAdding] = useState(false);
const { addLine, openCart, setLanguageCode } = useSaleorCheckoutStore();
const { trackAddToCart } = useAnalytics();
const image = getProductImage(product);
const price = getProductPrice(product);
const saleorLocale = isValidLocale(locale) ? getSaleorLocale(locale) : "SR";
const localized = getLocalizedProduct(product, saleorLocale);
const variant = product.variants?.[0];
const isAvailable = (variant?.quantityAvailable || 0) > 0;
const productHref = locale === "sr" ? `/products/${localized.slug}` : `/${locale}/products/${localized.slug}`;
const handleAddToCart = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!variant?.id) return;
if (isValidLocale(locale)) {
setLanguageCode(locale);
}
setIsAdding(true);
try {
await addLine(variant.id, 1);
const priceAmount = variant?.pricing?.price?.gross?.amount || 0;
const currency = variant?.pricing?.price?.gross?.currency || "RSD";
trackAddToCart({
id: product.id,
name: localized.name,
price: priceAmount,
currency,
quantity: 1,
variant: variant.name,
});
openCart();
} finally {
setIsAdding(false);
}
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="group"
>
<Link href={productHref} className="block">
<div className="relative w-full aspect-square bg-[#f8f9fa] overflow-hidden mb-4">
{image ? (
<Image
src={image}
alt={localized.name}
fill
className="object-cover object-center transition-transform duration-700 ease-out group-hover:scale-105"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
loading={index < 4 ? "eager" : "lazy"}
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-[#999999]">
<span className="text-sm">{t("noImage")}</span>
</div>
)}
{!isAvailable && (
<div className="absolute inset-0 bg-white/80 flex items-center justify-center">
<span className="text-sm uppercase tracking-[0.1em] text-[#666666]">
{t("outOfStock")}
</span>
</div>
)}
</div>
</Link>
<div className="text-center">
<Link href={productHref}>
<h3 className="text-[15px] font-medium text-[#1a1a1a] mb-1 group-hover:text-[#666666] transition-colors line-clamp-1">
{localized.name}
</h3>
</Link>
<p className="text-[14px] text-[#666666] mb-3">
{price || t("contactForPrice")}
</p>
{isAvailable ? (
<button
onClick={handleAddToCart}
disabled={isAdding}
className="w-full py-3 bg-black text-white text-xs uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isAdding ? tProduct("adding") : tProduct("addToCart")}
</button>
) : (
<div className="w-full py-3 bg-[#f8f9fa] text-[#666666] text-xs uppercase tracking-[0.1em]">
{t("outOfStock")}
</div>
)}
</div>
</motion.div>
);
}
export default function ProductsGrid({ products, locale }: ProductsGridProps) {
const t = useTranslations("Solutions");
const validProducts = products.filter(p => p && p.id);
return (
<section className="py-16 lg:py-24 bg-[#1A1A1A]">
<div className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-light text-white mb-4">
{t("completeYourRoutine")}
</h2>
<p className="text-[#999999] max-w-2xl mx-auto">
{t("discoverProducts")}
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
{validProducts.map((product, index) => (
<ProductCardWithAddToCart
key={product.id}
product={product}
index={index}
locale={locale}
/>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,57 @@
"use client";
import Link from "next/link";
import { ChevronRight, Home } from "lucide-react";
interface BreadcrumbItem {
label: string;
href?: string;
}
interface BreadcrumbProps {
items: BreadcrumbItem[];
locale: string;
showHome?: boolean;
}
export default function Breadcrumb({ items, locale, showHome = true }: BreadcrumbProps) {
const allItems = showHome
? [{ label: "Home", href: `/${locale}` }, ...items]
: items;
return (
<nav className="flex items-center gap-2 text-sm text-[#666666]" aria-label="Breadcrumb">
<ol className="flex items-center gap-2 flex-wrap">
{allItems.map((item, index) => {
const isLast = index === allItems.length - 1;
return (
<li key={index} className="flex items-center gap-2">
{index > 0 && <ChevronRight className="w-4 h-4 flex-shrink-0" />}
{isLast || !item.href ? (
<span className={isLast ? "text-[#1a1a1a]" : ""} aria-current={isLast ? "page" : undefined}>
{index === 0 && showHome ? (
<Home className="w-4 h-4" />
) : (
item.label
)}
</span>
) : (
<Link
href={item.href}
className="hover:text-black transition-colors"
>
{index === 0 && showHome ? (
<Home className="w-4 h-4" />
) : (
item.label
)}
</Link>
)}
</li>
);
})}
</ol>
</nav>
);
}

View File

@@ -180,6 +180,11 @@
"hairCare": "Haarpflege", "hairCare": "Haarpflege",
"skinCare": "Hautpflege", "skinCare": "Hautpflege",
"giftSets": "Geschenksets", "giftSets": "Geschenksets",
"solutions": "Lösungen",
"allSolutions": "Alle Lösungen",
"byConcern": "Nach Problem",
"byOil": "Nach Öl",
"skincareGuide": "Hautpflege-Guide",
"about": "Über uns", "about": "Über uns",
"ourStory": "Unsere Geschichte", "ourStory": "Unsere Geschichte",
"process": "Prozess", "process": "Prozess",
@@ -310,6 +315,10 @@
"quickAdd": "Schnell hinzufügen", "quickAdd": "Schnell hinzufügen",
"contactForPrice": "Preis anfragen" "contactForPrice": "Preis anfragen"
}, },
"Product": {
"adding": "Wird hinzugefügt...",
"addToCart": "In den Warenkorb"
},
"ProductDetail": { "ProductDetail": {
"home": "Startseite", "home": "Startseite",
"outOfStock": "Nicht auf Lager", "outOfStock": "Nicht auf Lager",
@@ -439,5 +448,107 @@
"description": "Bezahlen Sie per Banküberweisung", "description": "Bezahlen Sie per Banküberweisung",
"comingSoon": "Demnächst verfügbar" "comingSoon": "Demnächst verfügbar"
} }
},
"Solutions": {
"breadcrumb": {
"home": "Startseite",
"solutions": "Lösungen",
"byConcern": "Nach Problem",
"byOil": "Nach Öl"
},
"Hub": {
"metaTitle": "Natürliche Hautpflege-Lösungen | ManoonOils",
"metaDescription": "Entdecken Sie natürliche Öl-Lösungen für jedes Hautproblem. Durchsuchen Sie nach Problem, Öltyp oder erkunden Sie unsere umfassenden Hautpflege-Guides.",
"title": "Natürliche Hautpflege-Lösungen",
"subtitle": "Entdecken Sie die perfekten natürlichen Öl-Lösungen für Ihre Hautprobleme. Unsere fachkundig erstellten Guides helfen Ihnen, die richtigen Öle für Falten, Akne, Trockenheit und mehr zu finden.",
"categories": {
"oilForConcern": {
"title": "Öl für Problem",
"description": "Finden Sie die besten natürlichen Öle für spezifische Hautprobleme wie Falten, Akne, dunkle Flecken und mehr."
},
"ageSkinRoutine": {
"title": "Alter-Haut Routine",
"description": "Personalisierte Hautpflege-Routinen basierend auf Ihrem Alter und Hauttyp für optimale Ergebnisse."
},
"ingredientPairings": {
"title": "Inhaltsstoff-Kombinationen",
"description": "Lernen Sie, welche natürlichen Inhaltsstoffe am besten zusammenwirken für verbesserte Hautpflege-Vorteile."
},
"bodyPartConcerns": {
"title": "Körperteil-Probleme",
"description": "Gezielte Lösungen für spezifische Körperbereiche wie Gesicht, Hals, Hände und mehr."
},
"oilComparisons": {
"title": "Öl-Vergleiche",
"description": "Vergleiche nebeneinander, um Ihnen bei der Wahl zwischen verschiedenen natürlichen Ölen zu helfen."
},
"routineStepSkinType": {
"title": "Routine nach Hauttyp",
"description": "Schritt-für-Schritt-Anleitung, zugeschnitten auf Ihren spezifischen Hauttyp und Ihre Probleme."
},
"seasonalSkincare": {
"title": "Saisonale Hautpflege",
"description": "Passen Sie Ihre Routine den Jahreszeiten an für gesunde Haut das ganze Jahr über."
},
"timeOfDayConcerns": {
"title": "Tageszeit",
"description": "Morgen- und Abend-Hautpflege-Routinen für maximale Wirksamkeit."
},
"naturalAlternatives": {
"title": "Natürliche Alternativen",
"description": "Entdecken Sie natürliche Alternativen zu synthetischen Hautpflege-Inhaltsstoffen."
},
"culturalBeautySecrets": {
"title": "Kulturelle Schönheitsgeheimnisse",
"description": "Uralte Schönheitsweisheit aus der ganzen Welt mit natürlichen Ölen."
}
},
"quickAccess": {
"byConcern": "Nach Problem durchsuchen",
"byConcernDesc": "Finden Sie Lösungen für Ihre spezifischen Hautprobleme",
"byOil": "Nach Öl durchsuchen",
"byOilDesc": "Erkunden Sie die Vorteile verschiedener natürlicher Öle",
"links": {
"wrinkles": "Falten & Aging",
"acne": "Akne & Unreinheiten",
"drySkin": "Trockene Haut",
"darkSpots": "Dunkle Flecken",
"viewAll": "Alle ansehen →",
"rosehipOil": "Hagebuttenöl",
"arganOil": "Arganöl",
"jojobaOil": "Jojobaöl",
"seaBuckthornOil": "Sanddornöl"
}
},
"cta": {
"title": "Bereit, Ihre Haut zu verwandeln?",
"description": "Durchstöbern Sie unsere Kollektion von Premium-Naturölen und beginnen Sie noch heute Ihre Reise zu gesünderer, strahlender Haut.",
"button": "Naturöle kaufen"
}
},
"ByConcern": {
"metaTitle": "Hautpflege-Lösungen nach Problem | ManoonOils",
"metaDescription": "Durchsuchen Sie natürliche Öl-Lösungen nach Hautproblem organisiert. Finden Sie das perfekte Heilmittel für Falten, Akne, Trockenheit und mehr.",
"title": "Lösungen nach Problem",
"subtitle": "Erkunden Sie unsere umfassende Kollektion natürlicher Öl-Lösungen, organisiert nach Hautproblem, um Ihnen zu helfen, genau das zu finden, was Sie brauchen.",
"stats": {
"availableConcerns": "{count} Hautprobleme abgedeckt",
"totalSolutions": "{count} fachkundig kuratierte Lösungen"
},
"noResults": "Keine Probleme gefunden. Bitte schauen Sie später für neue Lösungen vorbei."
},
"ByOil": {
"metaTitle": "Hautpflege-Lösungen nach Öl | ManoonOils",
"metaDescription": "Entdecken Sie die Vorteile verschiedener natürlicher Öle für verschiedene Hautprobleme. Finden Sie heraus, welches Öl das richtige für Sie ist.",
"title": "Lösungen nach Öl",
"subtitle": "Lernen Sie die einzigartigen Eigenschaften jedes natürlichen Öls kennen und entdecken Sie, welche am besten für Ihre Hautprobleme geeignet sind.",
"stats": {
"availableOils": "{count} natürliche Öle",
"totalSolutions": "{count} fachkundig kuratierte Lösungen"
},
"noResults": "Keine Öle gefunden. Bitte schauen Sie später für neue Lösungen vorbei."
},
"completeYourRoutine": "Vervollständigen Sie Ihre Routine",
"discoverProducts": "Entdecken Sie unsere Premium-Produkte mit natürlichen Inhaltsstoffen"
} }
} }

View File

@@ -319,6 +319,11 @@
"hairCare": "Hair Care", "hairCare": "Hair Care",
"skinCare": "Skin Care", "skinCare": "Skin Care",
"giftSets": "Gift Sets", "giftSets": "Gift Sets",
"solutions": "Solutions",
"allSolutions": "All Solutions",
"byConcern": "By Concern",
"byOil": "By Oil",
"skincareGuide": "Skincare Guide",
"about": "About", "about": "About",
"ourStory": "Our Story", "ourStory": "Our Story",
"process": "Process", "process": "Process",
@@ -339,6 +344,10 @@
"quickAdd": "Quick Add", "quickAdd": "Quick Add",
"contactForPrice": "Contact for price" "contactForPrice": "Contact for price"
}, },
"Product": {
"adding": "Adding...",
"addToCart": "Add to Cart"
},
"ProductDetail": { "ProductDetail": {
"home": "Home", "home": "Home",
"outOfStock": "Out of Stock", "outOfStock": "Out of Stock",
@@ -494,5 +503,107 @@
"goHome": "Go Home", "goHome": "Go Home",
"lookingFor": "Can't find what you're looking for?", "lookingFor": "Can't find what you're looking for?",
"searchSuggestion": "Try browsing our product collection or contact us for assistance." "searchSuggestion": "Try browsing our product collection or contact us for assistance."
},
"Solutions": {
"breadcrumb": {
"home": "Home",
"solutions": "Solutions",
"byConcern": "By Concern",
"byOil": "By Oil"
},
"Hub": {
"metaTitle": "Natural Skincare Solutions | ManoonOils",
"metaDescription": "Discover natural oil solutions for every skin concern. Browse by concern, oil type, or explore our comprehensive skincare guides.",
"title": "Natural Skincare Solutions",
"subtitle": "Discover the perfect natural oil solutions for your skin concerns. Our expertly crafted guides help you find the right oils for wrinkles, acne, dryness, and more.",
"categories": {
"oilForConcern": {
"title": "Oil for Concern",
"description": "Find the best natural oils for specific skin concerns like wrinkles, acne, dark spots, and more."
},
"ageSkinRoutine": {
"title": "Age-Skin Routine",
"description": "Personalized skincare routines based on your age and skin type for optimal results."
},
"ingredientPairings": {
"title": "Ingredient Pairings",
"description": "Learn which natural ingredients work best together for enhanced skincare benefits."
},
"bodyPartConcerns": {
"title": "Body Part Concerns",
"description": "Targeted solutions for specific body areas like face, neck, hands, and more."
},
"oilComparisons": {
"title": "Oil Comparisons",
"description": "Side-by-side comparisons to help you choose between different natural oils."
},
"routineStepSkinType": {
"title": "Routine Step by Skin Type",
"description": "Step-by-step guidance tailored to your specific skin type and concerns."
},
"seasonalSkincare": {
"title": "Seasonal Skincare",
"description": "Adjust your routine with the seasons for year-round healthy skin."
},
"timeOfDayConcerns": {
"title": "Time of Day Concerns",
"description": "Morning and evening skincare routines for maximum effectiveness."
},
"naturalAlternatives": {
"title": "Natural Alternatives",
"description": "Discover natural alternatives to synthetic skincare ingredients."
},
"culturalBeautySecrets": {
"title": "Cultural Beauty Secrets",
"description": "Ancient beauty wisdom from around the world using natural oils."
}
},
"quickAccess": {
"byConcern": "Browse by Concern",
"byConcernDesc": "Find solutions for your specific skin concerns",
"byOil": "Browse by Oil",
"byOilDesc": "Explore benefits of different natural oils",
"links": {
"wrinkles": "Wrinkles & Aging",
"acne": "Acne & Blemishes",
"drySkin": "Dry Skin",
"darkSpots": "Dark Spots",
"viewAll": "View All →",
"rosehipOil": "Rosehip Oil",
"arganOil": "Argan Oil",
"jojobaOil": "Jojoba Oil",
"seaBuckthornOil": "Sea Buckthorn Oil"
}
},
"cta": {
"title": "Ready to Transform Your Skin?",
"description": "Browse our collection of premium natural oils and start your journey to healthier, more radiant skin today.",
"button": "Shop Natural Oils"
}
},
"ByConcern": {
"metaTitle": "Skincare Solutions by Concern | ManoonOils",
"metaDescription": "Browse natural oil solutions organized by skin concern. Find the perfect remedy for wrinkles, acne, dryness, and more.",
"title": "Solutions by Concern",
"subtitle": "Explore our comprehensive collection of natural oil solutions, organized by skin concern to help you find exactly what you need.",
"stats": {
"availableConcerns": "{count} skin concerns covered",
"totalSolutions": "{count} expert-curated solutions"
},
"noResults": "No concerns found. Please check back later for new solutions."
},
"ByOil": {
"metaTitle": "Skincare Solutions by Oil | ManoonOils",
"metaDescription": "Discover the benefits of different natural oils for various skin concerns. Find which oil is right for you.",
"title": "Solutions by Oil",
"subtitle": "Learn about the unique properties of each natural oil and discover which ones are best suited for your skin concerns.",
"stats": {
"availableOils": "{count} natural oils",
"totalSolutions": "{count} expert-curated solutions"
},
"noResults": "No oils found. Please check back later for new solutions."
},
"completeYourRoutine": "Complete Your Routine",
"discoverProducts": "Discover our premium products with natural ingredients"
} }
} }

View File

@@ -180,6 +180,11 @@
"hairCare": "Soins Capillaires", "hairCare": "Soins Capillaires",
"skinCare": "Soins Cutanés", "skinCare": "Soins Cutanés",
"giftSets": "Coffrets Cadeaux", "giftSets": "Coffrets Cadeaux",
"solutions": "Solutions",
"allSolutions": "Toutes les Solutions",
"byConcern": "Par Problème",
"byOil": "Par Huile",
"skincareGuide": "Guide Soins",
"about": "À Propos", "about": "À Propos",
"ourStory": "Notre Histoire", "ourStory": "Notre Histoire",
"process": "Processus", "process": "Processus",
@@ -310,6 +315,10 @@
"quickAdd": "Ajout Rapide", "quickAdd": "Ajout Rapide",
"contactForPrice": "Contacter pour le prix" "contactForPrice": "Contacter pour le prix"
}, },
"Product": {
"adding": "Ajout en cours...",
"addToCart": "Ajouter au Panier"
},
"ProductDetail": { "ProductDetail": {
"home": "Accueil", "home": "Accueil",
"outOfStock": "Rupture de Stock", "outOfStock": "Rupture de Stock",
@@ -439,5 +448,107 @@
"description": "Payez par virement bancaire", "description": "Payez par virement bancaire",
"comingSoon": "Bientôt disponible" "comingSoon": "Bientôt disponible"
} }
},
"Solutions": {
"breadcrumb": {
"home": "Accueil",
"solutions": "Solutions",
"byConcern": "Par Problème",
"byOil": "Par Huile"
},
"Hub": {
"metaTitle": "Solutions Naturelles pour la Peau | ManoonOils",
"metaDescription": "Découvrez les solutions à base d'huiles naturelles pour chaque problème de peau. Parcourez par problème, type d'huile ou explorez nos guides complets de soins.",
"title": "Solutions Naturelles pour la Peau",
"subtitle": "Découvrez les solutions d'huiles naturelles parfaites pour vos problèmes de peau. Nos guides créés par des experts vous aident à trouver les bonnes huiles pour les rides, l'acné, la sécheresse et plus encore.",
"categories": {
"oilForConcern": {
"title": "Huile pour Problème",
"description": "Trouvez les meilleures huiles naturelles pour des problèmes de peau spécifiques comme les rides, l'acné, les taches sombres et plus encore."
},
"ageSkinRoutine": {
"title": "Routine Âge-Peau",
"description": "Routines de soins personnalisées basées sur votre âge et type de peau pour des résultats optimaux."
},
"ingredientPairings": {
"title": "Associations d'Ingrédients",
"description": "Apprenez quels ingrédients naturels fonctionnent le mieux ensemble pour des bienfaits améliorés."
},
"bodyPartConcerns": {
"title": "Problèmes par Partie du Corps",
"description": "Solutions ciblées pour des zones spécifiques comme le visage, le cou, les mains et plus encore."
},
"oilComparisons": {
"title": "Comparaisons d'Huiles",
"description": "Comparaisons côte à côte pour vous aider à choisir entre différentes huiles naturelles."
},
"routineStepSkinType": {
"title": "Routine par Type de Peau",
"description": "Guide étape par étape adapté à votre type de peau spécifique et à vos problèmes."
},
"seasonalSkincare": {
"title": "Soins Saisonniers",
"description": "Adaptez votre routine aux saisons pour une peau saine toute l'année."
},
"timeOfDayConcerns": {
"title": "Moment de la Journée",
"description": "Routines de soins matinales et du soir pour une efficacité maximale."
},
"naturalAlternatives": {
"title": "Alternatives Naturelles",
"description": "Découvrez des alternatives naturelles aux ingrédients synthétiques de soins."
},
"culturalBeautySecrets": {
"title": "Secrets de Beauté Culturels",
"description": "Sagesse beauté ancestrale du monde entier utilisant des huiles naturelles."
}
},
"quickAccess": {
"byConcern": "Parcourir par Problème",
"byConcernDesc": "Trouvez des solutions pour vos problèmes de peau spécifiques",
"byOil": "Parcourir par Huile",
"byOilDesc": "Explorez les bienfaits des différentes huiles naturelles",
"links": {
"wrinkles": "Rides & Vieillissement",
"acne": "Acné & Imperfections",
"drySkin": "Peau Sèche",
"darkSpots": "Taches Sombres",
"viewAll": "Voir Tout →",
"rosehipOil": "Huile de Rose Musquée",
"arganOil": "Huile d'Argan",
"jojobaOil": "Huile de Jojoba",
"seaBuckthornOil": "Huile d'Argousier"
}
},
"cta": {
"title": "Prêt à Transformer Votre Peau?",
"description": "Parcourez notre collection d'huiles naturelles premium et commencez votre voyage vers une peau plus saine et éclatante dès aujourd'hui.",
"button": "Acheter les Huiles Naturelles"
}
},
"ByConcern": {
"metaTitle": "Solutions Soins par Problème | ManoonOils",
"metaDescription": "Parcourez les solutions d'huiles naturelles organisées par problème de peau. Trouvez le remède parfait pour les rides, l'acné, la sécheresse et plus encore.",
"title": "Solutions par Problème",
"subtitle": "Explorez notre collection complète de solutions d'huiles naturelles, organisées par problème de peau pour vous aider à trouver exactement ce dont vous avez besoin.",
"stats": {
"availableConcerns": "{count} problèmes de peau couverts",
"totalSolutions": "{count} solutions sélectionnées par des experts"
},
"noResults": "Aucun problème trouvé. Veuillez vérifier plus tard pour de nouvelles solutions."
},
"ByOil": {
"metaTitle": "Solutions Soins par Huile | ManoonOils",
"metaDescription": "Découvrez les bienfaits des différentes huiles naturelles pour divers problèmes de peau. Trouvez quelle huile est la bonne pour vous.",
"title": "Solutions par Huile",
"subtitle": "Apprenez les propriétés uniques de chaque huile naturelle et découvrez lesquelles conviennent le mieux à vos problèmes de peau.",
"stats": {
"availableOils": "{count} huiles naturelles",
"totalSolutions": "{count} solutions sélectionnées par des experts"
},
"noResults": "Aucune huile trouvée. Veuillez vérifier plus tard pour de nouvelles solutions."
},
"completeYourRoutine": "Complétez votre routine",
"discoverProducts": "Découvrez nos produits premium aux ingrédients naturels"
} }
} }

View File

@@ -319,6 +319,11 @@
"hairCare": "Nega kose", "hairCare": "Nega kose",
"skinCare": "Nega kože", "skinCare": "Nega kože",
"giftSets": "Poklon setovi", "giftSets": "Poklon setovi",
"solutions": "Rešenja",
"allSolutions": "Sva rešenja",
"byConcern": "Po problemu",
"byOil": "Po ulju",
"skincareGuide": "Vodič za negu",
"about": "O nama", "about": "O nama",
"ourStory": "Naša priča", "ourStory": "Naša priča",
"process": "Proces", "process": "Proces",
@@ -335,10 +340,14 @@
}, },
"ProductCard": { "ProductCard": {
"noImage": "Nema slike", "noImage": "Nema slike",
"outOfStock": "Nema na stanju", "outOfStock": "Nema na zalihama",
"quickAdd": "Brzo dodavanje", "quickAdd": "Brzo dodavanje",
"contactForPrice": "Kontaktirajte za cenu" "contactForPrice": "Kontaktirajte za cenu"
}, },
"Product": {
"adding": "Dodavanje...",
"addToCart": "Dodaj u korpu"
},
"ProductDetail": { "ProductDetail": {
"home": "Početna", "home": "Početna",
"outOfStock": "Nema na stanju", "outOfStock": "Nema na stanju",
@@ -493,5 +502,107 @@
"goHome": "Početna Strana", "goHome": "Početna Strana",
"lookingFor": "Ne možete da pronađete ono što tražite?", "lookingFor": "Ne možete da pronađete ono što tražite?",
"searchSuggestion": "Pokušajte da pregledate našu kolekciju proizvoda ili nas kontaktirajte za pomoć." "searchSuggestion": "Pokušajte da pregledate našu kolekciju proizvoda ili nas kontaktirajte za pomoć."
},
"Solutions": {
"breadcrumb": {
"home": "Početna",
"solutions": "Rešenja",
"byConcern": "Po problemu",
"byOil": "Po ulju"
},
"Hub": {
"metaTitle": "Prirodna rešenja za negu kože | ManoonOils",
"metaDescription": "Otkrijte prirodna uljana rešenja za svaki problem kože. Pretražujte po problemu, vrsti ulja ili istražite naše sveobuhvatne vodiče za negu kože.",
"title": "Prirodna rešenja za negu kože",
"subtitle": "Otkrijte savršena prirodna uljana rešenja za vaše probleme sa kožom. Naši stručno izrađeni vodiči pomažu vam da pronađete prava ulja za bore, akne, suvu kožu i još mnogo toga.",
"categories": {
"oilForConcern": {
"title": "Ulje za problem",
"description": "Pronađite najbolja prirodna ulja za specifične probleme kože poput bora, akni, tamnih fleka i još mnogo toga."
},
"ageSkinRoutine": {
"title": "Rutina prema uzrastu",
"description": "Personalizovane rutine nege kože na osnovu vašeg uzrasta i tipa kože za optimalne rezultate."
},
"ingredientPairings": {
"title": "Kombinacije sastojaka",
"description": "Saznajte koji prirodni sastojci najbolje rade zajedno za poboljšane koristi za kožu."
},
"bodyPartConcerns": {
"title": "Problemi po delovima tela",
"description": "Ciljana rešenja za specifične delove tela poput lica, vrata, ruku i još mnogo toga."
},
"oilComparisons": {
"title": "Poređenje ulja",
"description": "Poređenja jedno pored drugog da vam pomognu da izaberete između različitih prirodnih ulja."
},
"routineStepSkinType": {
"title": "Rutina prema tipu kože",
"description": "Vodič korak po korak prilagođen vašem specifičnom tipu kože i problemima."
},
"seasonalSkincare": {
"title": "Sezonska nega kože",
"description": "Prilagodite svoju rutinu godišnjim dobima za zdravu kožu tokom cele godine."
},
"timeOfDayConcerns": {
"title": "Vreme dana",
"description": "Jutarnje i večernje rutine nege kože za maksimalnu efikasnost."
},
"naturalAlternatives": {
"title": "Prirodne alternative",
"description": "Otkrijte prirodne alternative sintetičkim sastojcima za negu kože."
},
"culturalBeautySecrets": {
"title": "Kulturne tajne lepote",
"description": "Drevna mudrost lepote iz celog sveta korišćenjem prirodnih ulja."
}
},
"quickAccess": {
"byConcern": "Pretraži po problemu",
"byConcernDesc": "Pronađi rešenja za svoje probleme sa kožom",
"byOil": "Pretraži po ulju",
"byOilDesc": "Istraži prednosti različitih prirodnih ulja",
"links": {
"wrinkles": "Bore i starenje",
"acne": "Akne i nesavršenstva",
"drySkin": "Suva koža",
"darkSpots": "Tamne fleke",
"viewAll": "Pogledaj sve →",
"rosehipOil": "Ulje divlje ruže",
"arganOil": "Arganovo ulje",
"jojobaOil": "Jojoba ulje",
"seaBuckthornOil": "Ulje pasjeg trna"
}
},
"cta": {
"title": "Spremni za transformaciju kože?",
"description": "Pregledajte našu kolekciju premium prirodnih ulja i započnite svoje putovanje ka zdravijoj, sjajnijoj koži već danas.",
"button": "Kupi prirodna ulja"
}
},
"ByConcern": {
"metaTitle": "Rešenja za negu kože po problemu | ManoonOils",
"metaDescription": "Pregledajte prirodna uljana rešenja organizovana po problemima kože. Pronađite savršen lek za bore, akne, suvu kožu i još mnogo toga.",
"title": "Rešenja po problemu",
"subtitle": "Istražite našu sveobuhvatnu kolekciju prirodnih uljanih rešenja, organizovanih po problemima kože da vam pomognemo da pronađete tačno ono što vam treba.",
"stats": {
"availableConcerns": "{count} problema kože pokriveno",
"totalSolutions": "{count} stručno odabranih rešenja"
},
"noResults": "Nema pronađenih problema. Proverite ponovo kasnije za nova rešenja."
},
"ByOil": {
"metaTitle": "Rešenja za negu kože po ulju | ManoonOils",
"metaDescription": "Otkrijte prednosti različitih prirodnih ulja za različite probleme kože. Pronađite koje ulje je pravo za vas.",
"title": "Rešenja po ulju",
"subtitle": "Saznajte o jedinstvenim svojstvima svakog prirodnog ulja i otkrijte koja su najpogodnija za vaše probleme sa kožom.",
"stats": {
"availableOils": "{count} prirodnih ulja",
"totalSolutions": "{count} stručno odabranih rešenja"
},
"noResults": "Nema pronađenih ulja. Proverite ponovo kasnije za nova rešenja."
},
"completeYourRoutine": "Dovršite svoju rutinu",
"discoverProducts": "Otkrijte naše premium proizvode sa prirodnim sastojcima"
} }
} }

View File

@@ -0,0 +1,79 @@
import { readFile } from "fs/promises";
import { join } from "path";
import type {
OilForConcernPage,
LocalizedSEOKeywords
} from "./types";
const DATA_DIR = join(process.cwd(), "data");
export function getLocalizedString(
localized: { sr: string; en: string; de: string; fr: string },
locale: string
): string {
return localized[locale as keyof typeof localized] || localized.en;
}
export function getLocalizedArray(
localized: { sr: string[]; en: string[]; de: string[]; fr: string[] },
locale: string
): string[] {
return localized[locale as keyof typeof localized] || localized.en;
}
export function getLocalizedKeywords(
seoKeywords: LocalizedSEOKeywords,
locale: string
): string[] {
const keywords = seoKeywords[locale as keyof LocalizedSEOKeywords] || seoKeywords.en;
return [...keywords.primary, ...keywords.secondary, ...keywords.longTail];
}
export async function getOilForConcernPage(slug: string): Promise<OilForConcernPage | null> {
try {
const filePath = join(DATA_DIR, "oil-for-concern", `${slug}.json`);
const content = await readFile(filePath, "utf-8");
return JSON.parse(content) as OilForConcernPage;
} catch (error) {
console.error(`Failed to load oil-for-concern page: ${slug}`, error);
return null;
}
}
export async function getAllOilForConcernSlugs(): Promise<string[]> {
try {
const { readdir } = await import("fs/promises");
const dirPath = join(DATA_DIR, "oil-for-concern");
const files = await readdir(dirPath);
return files
.filter((file) => file.endsWith(".json"))
.map((file) => file.replace(".json", ""));
} catch (error) {
console.error("Failed to load oil-for-concern slugs:", error);
return [];
}
}
export async function getAllOilForConcernPages(): Promise<OilForConcernPage[]> {
const slugs = await getAllOilForConcernSlugs();
const pages = await Promise.all(
slugs.map((slug) => getOilForConcernPage(slug))
);
return pages.filter((page): page is OilForConcernPage => page !== null);
}
export async function getAllSolutionSlugs(): Promise<Array<{ locale: string; slug: string }>> {
const slugs = await getAllOilForConcernSlugs();
const result: Array<{ locale: string; slug: string }> = [];
for (const slug of slugs) {
for (const locale of ["sr", "en", "de", "fr"] as const) {
result.push({
locale,
slug
});
}
}
return result;
}

View File

@@ -0,0 +1,30 @@
export interface FAQPageSchema {
"@context": "https://schema.org";
"@type": "FAQPage";
mainEntity: Array<{
"@type": "Question";
name: string;
acceptedAnswer: {
"@type": "Answer";
text: string;
};
}>;
}
export function generateFAQPageSchema(
questions: Array<{ question: string; answer: string }>
): FAQPageSchema {
return {
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: questions.map((q) => ({
"@type": "Question",
name: q.question,
acceptedAnswer: {
"@type": "Answer",
text: q.answer,
},
})),
};
}

View File

@@ -0,0 +1,388 @@
export interface LocalizedString {
sr: string;
en: string;
de: string;
fr: string;
}
export interface LocalizedArray {
sr: string[];
en: string[];
de: string[];
fr: string[];
}
export interface SEOKeywords {
primary: string[];
secondary: string[];
longTail: string[];
}
export interface LocalizedSEOKeywords {
sr: SEOKeywords;
en: SEOKeywords;
de: SEOKeywords;
fr: SEOKeywords;
}
export interface FAQ {
question: LocalizedString;
answer: LocalizedString;
}
export interface BreadcrumbItem {
name: string;
url: string;
}
export interface RoutineStep {
step: number;
name: LocalizedString;
description: LocalizedString;
products: string[];
technique?: LocalizedString;
}
export interface ComparisonPoint {
category: LocalizedString;
oilAWins: boolean;
oilBWins: boolean;
explanation: LocalizedString;
}
export interface Testimonial {
quote: LocalizedString;
name: string;
age?: number;
skinType?: string;
timeframe?: string;
location?: string;
}
export interface OilForConcernPage {
slug: string;
oilSlug: string;
concernSlug: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
oilName: LocalizedString;
concernName: LocalizedString;
whyThisWorks: LocalizedString;
keyBenefits: LocalizedArray;
howToApply: LocalizedArray;
expectedResults: LocalizedString;
timeframe: LocalizedString;
complementaryIngredients: string[];
productsToShow: string[];
customerResults: Testimonial[];
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
relatedPages: {
otherOilsForSameConcern: string[];
sameOilForOtherConcerns: string[];
};
}
export interface AgeSkinTypeRoutinePage {
slug: string;
ageRange: string;
skinType: string;
season?: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
ageRangeLabel: LocalizedString;
skinTypeLabel: LocalizedString;
seasonLabel?: LocalizedString;
skinChangesAtThisAge: LocalizedArray;
whyThisRoutineWorks: LocalizedString;
morningRoutine: RoutineStep[];
eveningRoutine: RoutineStep[];
weeklyTreatments: RoutineStep[];
keyIngredients: string[];
productsToAvoid: LocalizedArray;
expectedResults: LocalizedString;
timeframe: LocalizedString;
productBundle: string[];
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
}
export interface IngredientPairingPage {
slug: string;
ingredientA: string;
ingredientB: string;
benefitSlug: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
ingredientAName: LocalizedString;
ingredientBName: LocalizedString;
benefitName: LocalizedString;
whyTheyWorkTogether: LocalizedString;
synergyExplanation: LocalizedString;
ingredientAContribution: LocalizedString;
ingredientBContribution: LocalizedString;
howToMix: LocalizedArray;
applicationSteps: LocalizedArray;
bestTimeToUse: LocalizedString;
skinTypesBestFor: string[];
whoShouldAvoid: LocalizedArray;
productsWithBoth: string[];
diyRecipe?: LocalizedString;
expectedResults: LocalizedString;
timeframe: LocalizedString;
customerTestimonials: Testimonial[];
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
alternativePairings: string[];
}
export interface BodyPartConcernPage {
slug: string;
bodyPart: string;
concernSlug: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
bodyPartName: LocalizedString;
concernName: LocalizedString;
whyThisAreaIsDifferent: LocalizedString;
commonCauses: LocalizedArray;
topOilsForThisArea: {
oilSlug: string;
reason: LocalizedString;
rank: number;
}[];
applicationTechnique: LocalizedString;
massageSteps: LocalizedArray;
frequency: LocalizedString;
complementaryTreatments: LocalizedArray;
lifestyleTips: LocalizedArray;
productsSpecificallyForArea: string[];
beforeAfterGallery: boolean;
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
}
export interface OilComparisonPage {
slug: string;
oilA: string;
oilB: string;
concernSlug: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
oilAName: LocalizedString;
oilBName: LocalizedString;
concernName: LocalizedString;
quickVerdict: LocalizedString;
winner: string | "tie" | "depends";
comparisonTable: ComparisonPoint[];
oilAPros: LocalizedArray;
oilACons: LocalizedArray;
oilBPros: LocalizedArray;
oilBCons: LocalizedArray;
chooseOilAIf: LocalizedArray;
chooseOilBIf: LocalizedArray;
detailedComparison: LocalizedString;
priceComparison: LocalizedString;
effectivenessComparison: LocalizedString;
gentlenessComparison: LocalizedString;
bestProducts: {
oilA: string[];
oilB: string[];
};
canYouUseBoth: LocalizedString;
howToCombine: LocalizedArray;
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
relatedComparisons: string[];
}
export interface RoutineStepSkinTypePage {
slug: string;
routineStep: string;
skinType: string;
concernSlug: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
routineStepName: LocalizedString;
skinTypeName: LocalizedString;
concernName: LocalizedString;
whyThisStepMatters: LocalizedString;
whatToLookFor: LocalizedArray;
ingredientsToAvoid: LocalizedArray;
topRecommendations: {
productName: string;
productSlug: string;
whyItWorks: LocalizedString;
keyIngredients: string[];
bestFor: LocalizedString;
rank: number;
}[];
applicationTips: LocalizedArray;
commonMistakes: LocalizedArray;
fullRoutineContext: {
previousStep?: LocalizedString;
nextStep?: LocalizedString;
};
expectedResults: LocalizedString;
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
}
export interface SeasonalSkincarePage {
slug: string;
season: string;
target: string;
targetType: "skinType" | "ageRange" | "concern";
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
seasonName: LocalizedString;
targetName: LocalizedString;
seasonalChallenges: LocalizedArray;
howSkinChanges: LocalizedString;
routineAdjustments: {
add: LocalizedArray;
remove: LocalizedArray;
modify: LocalizedArray;
};
morningRoutine: RoutineStep[];
eveningRoutine: RoutineStep[];
keyIngredients: string[];
ingredientsToAvoidThisSeason: LocalizedArray;
lifestyleTips: LocalizedArray;
nutritionTips: LocalizedArray;
seasonalProductBundle: string[];
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
}
export interface TimeOfDayConcernPage {
slug: string;
timeOfDay: string;
productType: string;
concernSlug: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
timeOfDayName: LocalizedString;
productTypeName: LocalizedString;
concernName: LocalizedString;
whyTimingMatters: LocalizedString;
skinBehaviorAtThisTime: LocalizedString;
whatHappensOvernight?: LocalizedString;
whyMorningRoutineMatters?: LocalizedString;
keyIngredientsToLookFor: LocalizedArray;
ingredientsToAvoid: LocalizedArray;
topRecommendations: {
productName: string;
productSlug: string;
whyItWorks: LocalizedString;
keyIngredients: string[];
rank: number;
}[];
applicationTips: LocalizedArray;
layeringOrder: LocalizedArray;
complementaryProducts: string[];
expectedResults: LocalizedString;
timeframe: LocalizedString;
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
relatedTimeSpecificPages: string[];
}
export interface NaturalAlternativePage {
slug: string;
syntheticIngredient: string;
concernSlug: string;
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
syntheticName: LocalizedString;
concernName: LocalizedString;
whyPeopleWantAlternatives: LocalizedString;
syntheticHowItWorks: LocalizedString;
syntheticSideEffects: LocalizedArray;
naturalAlternativeName: LocalizedString;
naturalAlternativeSlug: string;
howNaturalAlternativeWorks: LocalizedString;
effectivenessComparison: LocalizedString;
timelineComparison: LocalizedString;
gentlenessComparison: LocalizedString;
costComparison: LocalizedString;
whoShouldSwitch: LocalizedArray;
transitionGuide: LocalizedArray;
whatToExpect: LocalizedString;
bestProductsWithNaturalAlternative: string[];
diyOption?: LocalizedString;
customerStories: Testimonial[];
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
otherNaturalAlternatives: string[];
}
export interface CulturalBeautySecretPage {
slug: string;
region: string;
focus: string;
focusType: "ingredient" | "technique" | "ritual";
pageTitle: LocalizedString;
metaTitle: LocalizedString;
metaDescription: LocalizedString;
regionName: LocalizedString;
focusName: LocalizedString;
culturalBackground: LocalizedString;
historicalContext: LocalizedString;
traditionalUse: LocalizedString;
modernScienceValidation: LocalizedString;
keyIngredients: string[];
traditionalPreparation: LocalizedString;
modernAdaptation: LocalizedString;
applicationRitual: LocalizedArray;
bestTimeToUse: LocalizedString;
skinTypesBestFor: string[];
expectedResults: LocalizedString;
timeframe: LocalizedString;
productsInspiredByTradition: string[];
whereSourceFrom: LocalizedString;
sustainabilityNotes: LocalizedString;
customerExperiences: Testimonial[];
faqs: FAQ[];
seoKeywords: LocalizedSEOKeywords;
relatedCulturalPages: string[];
}
export type ProgrammaticPageType =
| "oil-for-concern"
| "age-skin-routine"
| "ingredient-pairing"
| "body-part-concern"
| "oil-comparison"
| "routine-step-skin"
| "seasonal-skincare"
| "time-of-day-concern"
| "natural-alternative"
| "cultural-beauty-secret";
export type ProgrammaticPage =
| OilForConcernPage
| AgeSkinTypeRoutinePage
| IngredientPairingPage
| BodyPartConcernPage
| OilComparisonPage
| RoutineStepSkinTypePage
| SeasonalSkincarePage
| TimeOfDayConcernPage
| NaturalAlternativePage
| CulturalBeautySecretPage;
export interface DataLoader<T> {
getAll(): Promise<T[]>;
getBySlug(slug: string): Promise<T | null>;
getBySlugs(slugs: string[]): Promise<T[]>;
}