|
| 1 | +import {Tab, Tabs} from 'nextra-theme-docs'; |
| 2 | +import Callout from 'components/Callout'; |
| 3 | + |
| 4 | +# Internationalization of sitemaps |
| 5 | + |
| 6 | +If you're using a sitemap to inform search engines about all pages of your site, you can attach [locale-specific alternate entries](https://developers.google.com/search/docs/specialty/international/localized-versions#sitemap) to every URL in the sitemap to indicate that a particular page is available in multiple languages or regions. |
| 7 | + |
| 8 | +Note that by default, `next-intl` returns [the `link` response header](/docs/routing/middleware#alternate-links) to instruct search engines that a page is available in multiple languages. While this sufficiently links localized pages for search engines, you may choose to provide this information in a sitemap in case you have more specific requirements. |
| 9 | + |
| 10 | +Next.js supports providing alternate URLs per language via the [`alternates` entry](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generate-a-localized-sitemap) as of version 14.2. You can use your default locale for the main URL and provide alternate URLs based on all locales that your app supports. Keep in mind that also the default locale should be included in the `alternates` object. |
| 11 | + |
| 12 | +<Tabs items={['Shared pathnames', 'Localized pathnames']}> |
| 13 | +<Tab> |
| 14 | + |
| 15 | +If you're using [shared pathnames](/docs/routing/navigation#shared-pathnames), you can iterate over an array of pathnames that your app supports and generate a sitemap entry for each pathname. |
| 16 | + |
| 17 | +**Example:** |
| 18 | + |
| 19 | +```tsx |
| 20 | +import {MetadataRoute} from 'next'; |
| 21 | + |
| 22 | +// Can be imported from shared config |
| 23 | +const defaultLocale = 'en' as const; |
| 24 | +const locales = ['en', 'de'] as const; |
| 25 | + |
| 26 | +// Adapt this as necessary |
| 27 | +const pathnames = ['/', '/about']; |
| 28 | +const host = 'https://acme.com'; |
| 29 | + |
| 30 | +export default function sitemap(): MetadataRoute.Sitemap { |
| 31 | + function getUrl(pathname: string, locale: string) { |
| 32 | + return `${host}/${locale}${pathname === '/' ? '' : pathname}`; |
| 33 | + } |
| 34 | + |
| 35 | + return pathnames.map((pathname) => ({ |
| 36 | + url: getUrl(pathname, defaultLocale), |
| 37 | + lastModified: new Date(), |
| 38 | + alternates: { |
| 39 | + languages: Object.fromEntries( |
| 40 | + locales.map((locale) => [locale, getUrl(pathname, locale)]) |
| 41 | + ) |
| 42 | + } |
| 43 | + })); |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +</Tab> |
| 48 | +<Tab> |
| 49 | + |
| 50 | +If you're using [localized pathnames](/docs/routing/navigation#localized-pathnames), you can use the keys of your already declared `pathnames` and generate an entry for each locale via the [`getPathname`](/docs/routing/navigation#getpathname) function. |
| 51 | + |
| 52 | +```tsx |
| 53 | +import {MetadataRoute} from 'next'; |
| 54 | +import {locales, pathnames, defaultLocale} from '@/config'; |
| 55 | +import {getPathname} from '@/navigation'; |
| 56 | + |
| 57 | +// Adapt this as necessary |
| 58 | +const host = 'https://acme.com'; |
| 59 | + |
| 60 | +export default function sitemap(): MetadataRoute.Sitemap { |
| 61 | + const keys = Object.keys(pathnames) as Array<keyof typeof pathnames>; |
| 62 | + |
| 63 | + function getUrl( |
| 64 | + key: keyof typeof pathnames, |
| 65 | + locale: (typeof locales)[number] |
| 66 | + ) { |
| 67 | + const pathname = getPathname({locale, href: key}); |
| 68 | + return `${HOST}/${locale}${pathname === '/' ? '' : pathname}`; |
| 69 | + } |
| 70 | + |
| 71 | + return keys.map((key) => ({ |
| 72 | + url: getUrl(key, defaultLocale), |
| 73 | + lastModified: new Date(), |
| 74 | + alternates: { |
| 75 | + languages: Object.fromEntries( |
| 76 | + locales.map((locale) => [locale, getUrl(key, locale)]) |
| 77 | + ) |
| 78 | + } |
| 79 | + })); |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +([working implementation](https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/app/sitemap.ts)) |
| 84 | + |
| 85 | +</Tab> |
| 86 | +</Tabs> |
| 87 | + |
| 88 | +<Callout> |
| 89 | +Note that your implementation may vary depending on your routing configuration (e.g. if you're using a [`localePrefix`](/docs/routing/middleware#locale-prefix) other than `always` or [locale-specific domains](/docs/routing/middleware#domain-based-routing)). |
| 90 | +</Callout> |
0 commit comments