fix(seo): generate correct sitemap URLs for default locale
Sitemap was generating /en/* URLs which redirect to unprefixed paths due to localePrefix: 'as-needed', causing Google Search Console "Page with redirect" warnings and wasting crawl budget. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,13 @@ const BASE_URL =
|
|||||||
process.env.NEXT_PUBLIC_APP_URL || 'https://skills.palebluedot.live';
|
process.env.NEXT_PUBLIC_APP_URL || 'https://skills.palebluedot.live';
|
||||||
|
|
||||||
const locales = ['en', 'fa'] as const;
|
const locales = ['en', 'fa'] as const;
|
||||||
|
const DEFAULT_LOCALE = 'en';
|
||||||
|
|
||||||
|
/** Get canonical URL path for a locale (no prefix for default locale, matching localePrefix: 'as-needed') */
|
||||||
|
function canonicalPath(locale: string, route: string): string {
|
||||||
|
if (locale === DEFAULT_LOCALE) return route || '/';
|
||||||
|
return `/${locale}${route}`;
|
||||||
|
}
|
||||||
|
|
||||||
const staticRoutes = [
|
const staticRoutes = [
|
||||||
'',
|
'',
|
||||||
@@ -27,7 +34,8 @@ const staticRoutes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
function makeEntry(
|
function makeEntry(
|
||||||
path: string,
|
locale: string,
|
||||||
|
route: string,
|
||||||
options?: {
|
options?: {
|
||||||
lastModified?: Date;
|
lastModified?: Date;
|
||||||
changeFrequency?: MetadataRoute.Sitemap[number]['changeFrequency'];
|
changeFrequency?: MetadataRoute.Sitemap[number]['changeFrequency'];
|
||||||
@@ -35,17 +43,13 @@ function makeEntry(
|
|||||||
}
|
}
|
||||||
): MetadataRoute.Sitemap[number] {
|
): MetadataRoute.Sitemap[number] {
|
||||||
return {
|
return {
|
||||||
url: `${BASE_URL}${path}`,
|
url: `${BASE_URL}${canonicalPath(locale, route)}`,
|
||||||
lastModified: options?.lastModified,
|
lastModified: options?.lastModified,
|
||||||
changeFrequency: options?.changeFrequency,
|
changeFrequency: options?.changeFrequency,
|
||||||
priority: options?.priority,
|
priority: options?.priority,
|
||||||
alternates: {
|
alternates: {
|
||||||
languages: Object.fromEntries(
|
languages: Object.fromEntries(
|
||||||
locales.map((locale) => {
|
locales.map((l) => [l, `${BASE_URL}${canonicalPath(l, route)}`])
|
||||||
// For root path, use /en or /fa; for others, prefix with locale
|
|
||||||
const localePath = path.replace(/^\/(en|fa)/, `/${locale}`);
|
|
||||||
return [locale, `${BASE_URL}${localePath}`];
|
|
||||||
})
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -64,10 +68,9 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
// Static pages (for each locale)
|
// Static pages (for each locale)
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
for (const route of staticRoutes) {
|
for (const route of staticRoutes) {
|
||||||
const path = `/${locale}${route}`;
|
|
||||||
const isHome = route === '';
|
const isHome = route === '';
|
||||||
entries.push(
|
entries.push(
|
||||||
makeEntry(path, {
|
makeEntry(locale, route, {
|
||||||
changeFrequency: isHome ? 'daily' : 'weekly',
|
changeFrequency: isHome ? 'daily' : 'weekly',
|
||||||
priority: isHome ? 1.0 : 0.5,
|
priority: isHome ? 1.0 : 0.5,
|
||||||
})
|
})
|
||||||
@@ -82,7 +85,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
for (const skill of allSkills) {
|
for (const skill of allSkills) {
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
entries.push(
|
entries.push(
|
||||||
makeEntry(`/${locale}/skill/${skill.id}`, {
|
makeEntry(locale, `/skill/${skill.id}`, {
|
||||||
lastModified: skill.updatedAt,
|
lastModified: skill.updatedAt,
|
||||||
changeFrequency: 'weekly',
|
changeFrequency: 'weekly',
|
||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
@@ -103,7 +106,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
for (const [owner, lastModified] of uniqueOwners) {
|
for (const [owner, lastModified] of uniqueOwners) {
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
entries.push(
|
entries.push(
|
||||||
makeEntry(`/${locale}/owner/${owner}`, {
|
makeEntry(locale, `/owner/${owner}`, {
|
||||||
lastModified,
|
lastModified,
|
||||||
changeFrequency: 'weekly',
|
changeFrequency: 'weekly',
|
||||||
priority: 0.6,
|
priority: 0.6,
|
||||||
@@ -121,7 +124,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
for (const category of allCategories) {
|
for (const category of allCategories) {
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
entries.push(
|
entries.push(
|
||||||
makeEntry(`/${locale}/browse?category=${category.slug}`, {
|
makeEntry(locale, `/browse?category=${category.slug}`, {
|
||||||
changeFrequency: 'weekly',
|
changeFrequency: 'weekly',
|
||||||
priority: 0.5,
|
priority: 0.5,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user