feat: add hreflang tags and international sitemap for SEO
- Added hreflang alternates to root layout for all locales (sr, en, de, fr) - Added hreflang alternates to [locale] layout for all locales - Updated sitemap to include all locale variants for every page - Google will now properly index all language versions
This commit is contained in:
@@ -1,11 +1,36 @@
|
||||
import { Metadata } from "next";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages, setRequestLocale } from "next-intl/server";
|
||||
import { routing } from "@/i18n/routing";
|
||||
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const localePrefix = locale === "sr" ? "" : `/${locale}`;
|
||||
|
||||
const languages: Record<string, string> = {};
|
||||
for (const loc of routing.locales) {
|
||||
const prefix = loc === "sr" ? "" : `/${loc}`;
|
||||
languages[loc] = `${baseUrl}${prefix}`;
|
||||
}
|
||||
|
||||
return {
|
||||
alternates: {
|
||||
canonical: `${baseUrl}${localePrefix}`,
|
||||
languages,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params,
|
||||
|
||||
@@ -2,6 +2,8 @@ import "./globals.css";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import ErrorBoundary from "@/components/providers/ErrorBoundary";
|
||||
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||
@@ -9,6 +11,15 @@ export const metadata: Metadata = {
|
||||
},
|
||||
description: "Discover our premium collection of natural oils for hair and skin care.",
|
||||
robots: "index, follow",
|
||||
alternates: {
|
||||
canonical: baseUrl,
|
||||
languages: {
|
||||
sr: baseUrl,
|
||||
en: `${baseUrl}/en`,
|
||||
de: `${baseUrl}/de`,
|
||||
fr: `${baseUrl}/fr`,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||
description: "Discover our premium collection of natural oils for hair and skin care.",
|
||||
|
||||
@@ -1,48 +1,121 @@
|
||||
import { MetadataRoute } from "next";
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const LOCALES = ["sr", "en", "de", "fr"] as const;
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||
|
||||
interface SitemapEntry {
|
||||
url: string;
|
||||
lastModified: Date;
|
||||
changeFrequency: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
||||
priority: number;
|
||||
alternates?: {
|
||||
languages?: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
export default async function sitemap(): Promise<SitemapEntry[]> {
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts("SR", 100);
|
||||
} catch (e) {
|
||||
console.log('Failed to fetch products for sitemap during build');
|
||||
console.log("Failed to fetch products for sitemap during build");
|
||||
}
|
||||
|
||||
const productUrls = products.map((product) => ({
|
||||
url: `${baseUrl}/products/${product.slug}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly" as const,
|
||||
priority: 0.8,
|
||||
}));
|
||||
|
||||
return [
|
||||
const staticPages: SitemapEntry[] = [
|
||||
{
|
||||
url: baseUrl,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 1,
|
||||
alternates: {
|
||||
languages: {
|
||||
sr: `${baseUrl}`,
|
||||
en: `${baseUrl}/en`,
|
||||
de: `${baseUrl}/de`,
|
||||
fr: `${baseUrl}/fr`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/products`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 0.9,
|
||||
alternates: {
|
||||
languages: {
|
||||
sr: `${baseUrl}/products`,
|
||||
en: `${baseUrl}/en/products`,
|
||||
de: `${baseUrl}/de/products`,
|
||||
fr: `${baseUrl}/fr/products`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/about`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.6,
|
||||
alternates: {
|
||||
languages: {
|
||||
sr: `${baseUrl}/about`,
|
||||
en: `${baseUrl}/en/about`,
|
||||
de: `${baseUrl}/de/about`,
|
||||
fr: `${baseUrl}/fr/about`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/contact`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.6,
|
||||
alternates: {
|
||||
languages: {
|
||||
sr: `${baseUrl}/contact`,
|
||||
en: `${baseUrl}/en/contact`,
|
||||
de: `${baseUrl}/de/contact`,
|
||||
fr: `${baseUrl}/fr/contact`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/checkout`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.5,
|
||||
alternates: {
|
||||
languages: {
|
||||
sr: `${baseUrl}/checkout`,
|
||||
en: `${baseUrl}/en/checkout`,
|
||||
de: `${baseUrl}/de/checkout`,
|
||||
fr: `${baseUrl}/fr/checkout`,
|
||||
},
|
||||
},
|
||||
},
|
||||
...productUrls,
|
||||
];
|
||||
|
||||
const productUrls: SitemapEntry[] = [];
|
||||
|
||||
for (const product of products) {
|
||||
for (const locale of LOCALES) {
|
||||
const localePrefix = locale === "sr" ? "" : `/${locale}`;
|
||||
productUrls.push({
|
||||
url: `${baseUrl}${localePrefix}/products/${product.slug}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly",
|
||||
priority: 0.8,
|
||||
alternates: {
|
||||
languages: {
|
||||
sr: `${baseUrl}/products/${product.slug}`,
|
||||
en: `${baseUrl}/en/products/${product.slug}`,
|
||||
de: `${baseUrl}/de/products/${product.slug}`,
|
||||
fr: `${baseUrl}/fr/products/${product.slug}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return [...staticPages, ...productUrls];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user