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 { NextIntlClientProvider } from "next-intl";
|
||||||
import { getMessages, setRequestLocale } from "next-intl/server";
|
import { getMessages, setRequestLocale } from "next-intl/server";
|
||||||
import { routing } from "@/i18n/routing";
|
import { routing } from "@/i18n/routing";
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return routing.locales.map((locale) => ({ locale }));
|
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({
|
export default async function LocaleLayout({
|
||||||
children,
|
children,
|
||||||
params,
|
params,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import "./globals.css";
|
|||||||
import type { Metadata, Viewport } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import ErrorBoundary from "@/components/providers/ErrorBoundary";
|
import ErrorBoundary from "@/components/providers/ErrorBoundary";
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
default: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
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.",
|
description: "Discover our premium collection of natural oils for hair and skin care.",
|
||||||
robots: "index, follow",
|
robots: "index, follow",
|
||||||
|
alternates: {
|
||||||
|
canonical: baseUrl,
|
||||||
|
languages: {
|
||||||
|
sr: baseUrl,
|
||||||
|
en: `${baseUrl}/en`,
|
||||||
|
de: `${baseUrl}/de`,
|
||||||
|
fr: `${baseUrl}/fr`,
|
||||||
|
},
|
||||||
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||||
description: "Discover our premium collection of natural oils for hair and skin care.",
|
description: "Discover our premium collection of natural oils for hair and skin care.",
|
||||||
|
|||||||
@@ -1,48 +1,121 @@
|
|||||||
import { MetadataRoute } from "next";
|
import { MetadataRoute } from "next";
|
||||||
import { getProducts } from "@/lib/saleor";
|
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";
|
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[] = [];
|
let products: any[] = [];
|
||||||
try {
|
try {
|
||||||
products = await getProducts("SR", 100);
|
products = await getProducts("SR", 100);
|
||||||
} catch (e) {
|
} 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) => ({
|
const staticPages: SitemapEntry[] = [
|
||||||
url: `${baseUrl}/products/${product.slug}`,
|
|
||||||
lastModified: new Date(),
|
|
||||||
changeFrequency: "weekly" as const,
|
|
||||||
priority: 0.8,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
url: baseUrl,
|
url: baseUrl,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: "daily",
|
changeFrequency: "daily",
|
||||||
priority: 1,
|
priority: 1,
|
||||||
|
alternates: {
|
||||||
|
languages: {
|
||||||
|
sr: `${baseUrl}`,
|
||||||
|
en: `${baseUrl}/en`,
|
||||||
|
de: `${baseUrl}/de`,
|
||||||
|
fr: `${baseUrl}/fr`,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/products`,
|
url: `${baseUrl}/products`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: "daily",
|
changeFrequency: "daily",
|
||||||
priority: 0.9,
|
priority: 0.9,
|
||||||
|
alternates: {
|
||||||
|
languages: {
|
||||||
|
sr: `${baseUrl}/products`,
|
||||||
|
en: `${baseUrl}/en/products`,
|
||||||
|
de: `${baseUrl}/de/products`,
|
||||||
|
fr: `${baseUrl}/fr/products`,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/about`,
|
url: `${baseUrl}/about`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.6,
|
priority: 0.6,
|
||||||
|
alternates: {
|
||||||
|
languages: {
|
||||||
|
sr: `${baseUrl}/about`,
|
||||||
|
en: `${baseUrl}/en/about`,
|
||||||
|
de: `${baseUrl}/de/about`,
|
||||||
|
fr: `${baseUrl}/fr/about`,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/contact`,
|
url: `${baseUrl}/contact`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.6,
|
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