Skip to content

Commit 367df59

Browse files
committed
fix hreflang tags to only include translated locales
1 parent e8d7806 commit 367df59

File tree

1 file changed

+38
-11
lines changed

1 file changed

+38
-11
lines changed

src/lib/utils/metadata.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server"
33

44
import { DEFAULT_OG_IMAGE, SITE_URL } from "@/lib/constants"
55

6+
import { getTranslatedLocales } from "../i18n/translationRegistry"
7+
68
import { isLocaleValidISO639_1 } from "./translations"
79
import { getFullUrl } from "./url"
810

@@ -43,6 +45,7 @@ export const getMetadata = async ({
4345
image,
4446
author,
4547
noIndex = false,
48+
translatedLocales,
4649
}: {
4750
locale: string
4851
slug: string[]
@@ -52,36 +55,56 @@ export const getMetadata = async ({
5255
image?: string
5356
author?: string
5457
noIndex?: boolean
58+
translatedLocales?: string[]
5559
}): Promise<Metadata> => {
5660
const slugString = slug.join("/")
5761
const t = await getTranslations({ locale, namespace: "common" })
5862

5963
const description = descriptionProp || t("site-description")
6064
const siteTitle = t("site-title")
6165

62-
// Set canonical URL w/ language path to avoid duplicate content
63-
const url = getFullUrl(locale, slugString)
66+
// Auto-detect translated locales if not provided
67+
const finalTranslatedLocales =
68+
translatedLocales ?? (await getTranslatedLocales(slugString))
69+
70+
const isCurrentPageTranslated = finalTranslatedLocales.includes(locale)
6471

65-
// Set x-default URL for hreflang
72+
// Set canonical URL
73+
// If current locale is NOT translated, set canonical to English version
74+
const canonicalLocale = isCurrentPageTranslated
75+
? locale
76+
: routing.defaultLocale
77+
const url = getFullUrl(canonicalLocale, slugString)
78+
79+
// Set x-default URL for hreflang (always use default locale)
6680
const xDefault = getFullUrl(routing.defaultLocale, slugString)
6781

6882
/* Set fallback ogImage based on path */
6983
const ogImage = image || getOgImage(slug)
7084

85+
// Only include hreflang alternates if the current page is translated
86+
// Untranslated pages should not have hreflang tags
87+
const localesForHreflang = isCurrentPageTranslated
88+
? routing.locales.filter(
89+
(loc) =>
90+
finalTranslatedLocales.includes(loc) && isLocaleValidISO639_1(loc)
91+
)
92+
: []
93+
7194
const base: Metadata = {
7295
title,
7396
description,
7497
metadataBase: new URL(SITE_URL),
7598
alternates: {
7699
canonical: url,
77-
languages: {
78-
"x-default": xDefault,
79-
...Object.fromEntries(
80-
routing.locales
81-
.filter(isLocaleValidISO639_1)
82-
.map((locale) => [locale, getFullUrl(locale, slugString)])
83-
),
84-
},
100+
...(localesForHreflang.length > 0 && {
101+
languages: {
102+
"x-default": xDefault,
103+
...Object.fromEntries(
104+
localesForHreflang.map((loc) => [loc, getFullUrl(loc, slugString)])
105+
),
106+
},
107+
}),
85108
},
86109
openGraph: {
87110
title,
@@ -117,5 +140,9 @@ export const getMetadata = async ({
117140
return { ...base, robots: { index: false } }
118141
}
119142

143+
if (!isCurrentPageTranslated) {
144+
return { ...base, robots: { index: true, follow: true } }
145+
}
146+
120147
return base
121148
}

0 commit comments

Comments
 (0)