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
This commit is contained in:
Unchained
2026-04-05 05:21:57 +02:00
parent 6caefb420a
commit f6609f07d7
22 changed files with 3263 additions and 8 deletions

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}
/>
</>
);
}