From c4b63a2692652a3179be4990c60ab3dd4ecd5d5e Mon Sep 17 00:00:00 2001 From: moss-bryophyta <261561981+moss-bryophyta@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:44:38 -0800 Subject: [PATCH] docs: add guide on locale aliases and SEO for gt-next --- docs/en-US/next/guides/locale-aliases.mdx | 205 ++++++++++++++++++++++ docs/en-US/next/meta.json | 1 + 2 files changed, 206 insertions(+) create mode 100644 docs/en-US/next/guides/locale-aliases.mdx diff --git a/docs/en-US/next/guides/locale-aliases.mdx b/docs/en-US/next/guides/locale-aliases.mdx new file mode 100644 index 0000000..4d3f4b5 --- /dev/null +++ b/docs/en-US/next/guides/locale-aliases.mdx @@ -0,0 +1,205 @@ +--- +title: Locale Aliases and SEO +description: Use custom locale aliases for URL routing while maintaining BCP 47 compliance for search engines +--- + +Locale aliases let you use custom locale codes in your URLs (e.g. `/cn/` instead of `/zh/`) while keeping your SEO metadata compliant with the [BCP 47 standard](https://www.w3.org/International/articles/language-tags/) that search engines expect. + +## Why aliases? + +BCP 47 locale codes like `zh` (Chinese) or `zh-Hant` (Traditional Chinese) are the standard for identifying languages on the web. +However, you might want different codes in your URL paths for branding, readability, or regional reasons — for example, using `/cn/` instead of `/zh/` for your Chinese audience. + +GT supports this through **custom mapping** in your `gt.config.json`. The alias is used for routing and URL paths, while the canonical BCP 47 code is used wherever search engines need it. + + +**SEO requirement:** Search engines only recognize [BCP 47 locale codes](https://www.w3.org/International/articles/language-tags/). +Using non-standard codes like `cn` in `hreflang` attributes or `` will cause search engines to ignore your locale signals. + + +## Setup + +### Step 1: Configure custom mapping + +Add a `customMapping` entry to your `gt.config.json` for each alias: + +```json title="gt.config.json" +{ + "defaultLocale": "en-US", + "locales": ["en-US", "cn", "ja", "zh-Hant"], + "customMapping": { + "cn": { + "code": "zh", + "name": "Mandarin" + } + } +} +``` + +Here, `cn` is the alias used in URLs and middleware routing, and `zh` is the canonical BCP 47 code. + +### Step 2: Use middleware as normal + +The [middleware](/docs/next/guides/middleware) and `[locale]` dynamic route work with your alias codes out of the box. Users visiting `/cn/about` will be served Chinese content — no special handling needed for routing. + +## BCP 47 compliance for SEO + +While aliases work seamlessly for routing, there are three places where you **must** use the canonical BCP 47 code instead of the alias: + +1. The `lang` attribute on your `` tag +2. Alternate link tags in your page metadata +3. Alternate entries in your sitemap + +GT provides the `resolveCanonicalLocale()` method to convert aliases back to their BCP 47 codes. You can access it via `getGTClass` from `gt-next/server`: + +```ts +import { getGTClass } from 'gt-next/server'; + +const gtInstance = getGTClass(); +const canonicalLocale = gtInstance.resolveCanonicalLocale('cn'); +// Returns: "zh" +``` + +For non-aliased locales, `resolveCanonicalLocale()` returns the input unchanged: + +```ts +gtInstance.resolveCanonicalLocale('ja'); // "ja" +gtInstance.resolveCanonicalLocale('en-US'); // "en-US" +``` + +### 1. HTML `lang` attribute + +The `` attribute tells browsers and search engines what language the page is in. It must be a valid BCP 47 code. + +In your root layout, resolve the locale before passing it to the `` tag: + +```tsx title="app/[locale]/layout.tsx" +import { getGTClass } from 'gt-next/server'; + +export default function RootLayout({ + children, + params, +}: { + children: React.ReactNode; + params: { locale: string }; +}) { + const gtInstance = getGTClass(); + const canonicalLocale = gtInstance.resolveCanonicalLocale(params.locale); + + return ( + + {children} + + ); +} +``` + +Without this, a page at `/cn/about` would incorrectly render ``, which search engines don't recognize. + +### 2. Metadata alternates + +Alternate links tell search engines which versions of a page exist in other languages. The `hreflang` attribute must use BCP 47 codes. + +```tsx title="app/[locale]/layout.tsx" +import type { Metadata } from 'next'; +import { getGTClass } from 'gt-next/server'; + +export async function generateMetadata({ + params, +}: { + params: { locale: string }; +}): Promise { + const gtInstance = getGTClass(); + const locales = ['en-US', 'cn', 'ja', 'zh-Hant']; + + // Build alternates with canonical BCP 47 codes as keys + const languages: Record = {}; + for (const locale of locales) { + const canonical = gtInstance.resolveCanonicalLocale(locale); + languages[canonical] = `https://example.com/${locale}`; + } + + // Add x-default for the default locale + languages['x-default'] = 'https://example.com'; + + return { + alternates: { + canonical: `https://example.com/${params.locale}`, + languages, + }, + }; +} +``` + +This produces correct `` tags in the page head: + +```html + + + + + +``` + +Notice that the `hreflang` values use canonical codes (`zh`, not `cn`), while the `href` URLs still use the alias paths (`/cn/`). + +### 3. Sitemap alternates + +If you use a [dynamic sitemap](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap), apply the same pattern: + +```ts title="app/sitemap.ts" +import type { MetadataRoute } from 'next'; +import { getGTClass } from 'gt-next/server'; + +export default function sitemap(): MetadataRoute.Sitemap { + const gtInstance = getGTClass(); + const locales = ['en-US', 'cn', 'ja', 'zh-Hant']; + const baseUrl = 'https://example.com'; + + const pages = ['', '/about', '/pricing']; + + return pages.map((page) => { + // Build language alternates with canonical codes + const languages: Record = {}; + for (const locale of locales) { + const canonical = gtInstance.resolveCanonicalLocale(locale); + languages[canonical] = `${baseUrl}/${locale}${page}`; + } + + return { + url: `${baseUrl}${page}`, + lastModified: new Date(), + alternates: { + languages, + }, + }; + }); +} +``` + +This generates sitemap XML with proper `hreflang` attributes: + +```xml + + https://example.com + + + + + +``` + +## Common mistakes + +| Mistake | Impact | Fix | +|---|---|---| +| Using alias code in `` | Search engines can't identify the page language | Use `resolveCanonicalLocale()` for the `lang` attribute | +| Using alias code in `hreflang` | Search engines ignore the alternate link | Use `resolveCanonicalLocale()` for `hreflang` values | +| Missing `x-default` alternate | No fallback for users whose language isn't listed | Add `x-default` pointing to your default locale URL | +| Inconsistent alternates between HTML and sitemap | Conflicting signals confuse crawlers | Use `resolveCanonicalLocale()` in both places | + +## Next steps + +- Learn about [middleware](/docs/next/guides/middleware) for locale-based URL routing +- See [`resolveCanonicalLocale`](/docs/core/class/methods/locales/resolve-canonical-locale) API reference +- Configure [`customMapping`](/docs/cli/reference/config) in your `gt.config.json` diff --git a/docs/en-US/next/meta.json b/docs/en-US/next/meta.json index b0ec7d5..a524905 100644 --- a/docs/en-US/next/meta.json +++ b/docs/en-US/next/meta.json @@ -18,6 +18,7 @@ "---Advanced Integration---", "./guides/local-tx", "./guides/middleware", + "./guides/locale-aliases", "./guides/ssg", "./guides/rtl", "./guides/migration",