# General Translation Knowledge Base: How do I set up SEO for a multilingual Next.js app?
URL: https://generaltranslation.com/en-US/kb/multilingual-nextjs-seo.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Give every language its own URL, set the html lang attribute, point each page's canonical URL at itself, add hreflang alternates for every language, translate titles and descriptions, and list every language version in your sitemap.
Authors: General Translation Team
Published: 2026-10-09

## Why multilingual SEO needs special attention

Most developers who add i18n to their Next.js app focus on translating the UI and call it done.
But if search engines can't find, index, and properly associate your language versions,
all that translation work is invisible.

A multilingual site without proper SEO setup has problems:

- Google may index only one language version and ignore the rest
- Users searching in Spanish get served the English page
- Duplicate content penalties because Google sees `/en/about` and `/fr/about` as the same page
- Incorrect language shown in search result snippets

**The good news:** getting multilingual SEO right in Next.js isn't complicated.
There are six things you need to get right, and this guide covers all of them using [`gt-next`](/docs/react/nextjs-quickstart).

---

## 1. Locale-based URL routing

The foundation of multilingual SEO is having distinct URLs for each language.
Search engines need separate, crawlable URLs to index each language version independently.

This means **locale-in-the-URL** — not cookies, not query parameters, not `Accept-Language` detection alone.

```
✅ generaltranslation.com/en/about
✅ generaltranslation.com/fr/about
✅ generaltranslation.com/es/about

❌ generaltranslation.com/about?lang=fr
❌ generaltranslation.com/about (with locale in a cookie)
```

### Setting up locale routing with gt-next

First, nest your pages under a `[locale]` dynamic segment:

```
app/
└── [locale]/
    ├── layout.tsx
    ├── page.tsx
    └── about/
        └── page.tsx
```

Then create middleware in your project root (`proxy.ts` for Next.js 16+, or `middleware.ts` for Next.js 15 and below):

```ts title="proxy.ts"
import { createNextMiddleware } from 'gt-next/middleware';

export default createNextMiddleware();

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next).*)'],
};
```

This gives you locale-prefixed URLs automatically.
By default, the default locale (e.g., English) doesn't get a prefix — `/about` stays clean,
while Spanish users see `/es/about` and French users see `/fr/about`.

---

## 2. Setting the HTML `lang` attribute

The `lang` attribute on your `<html>` tag tells browsers and search engines which language the page is in.
It's one of the simplest and most impactful things you can do for accessibility and SEO.

Without it, screen readers guess the language (often wrong), and search engines have less confidence in their language classification.

{/* useLocale works in both client and server rendered components, just not in async components */}
gt-next provides the [`useLocale`](/docs/react/reference/hooks/use-locale) hook that makes this trivial in your root layout:

```tsx title="app/[locale]/layout.tsx"
import { useLocale, GTProvider } from 'gt-next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = useLocale();

  return (
    <html lang={locale}>
      <body>
        <GTProvider>
          {children}
        </GTProvider>
      </body>
    </html>
  );
}
```

[`useLocale`](/docs/react/reference/hooks/use-locale) returns the BCP 47 locale code (e.g., `en-US`, `ar`, `zh-Hans`).

---

## 3. Canonical URLs

Canonical tags tell search engines which URL is the "primary" version of a page.
For multilingual sites, each language version should point to itself as the canonical:

```html
<!-- On /fr/about -->
<link rel="canonical" href="https://example.com/fr/about" />
```

This prevents search engines from treating your French page as a duplicate of your English page.

In Next.js, you set canonicals through the [metadata API](https://nextjs.org/docs/app/api-reference/functions/generate-metadata).
Combine it with gt-next's [`getLocale`](/docs/react/nextjs/reference/functions/get-locale) to generate the correct canonical for each locale:

```tsx title="app/[locale]/about/page.tsx"
import { getLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';

export async function generateMetadata() {
  const locale = await getLocale();

  return {
    alternates: {
      canonical: `${BASE_URL}/${locale}/about`,
    },
  };
}

export default function AboutPage() {
  return <h1>About Us</h1>;
}
```

For the default locale without a prefix, adjust accordingly:

```tsx
import { getDefaultLocale } from 'gt-next';
import { getLocale } from 'gt-next/server';

export async function generateMetadata() {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();
  const path = '/about';

  const prefix = locale === defaultLocale ? '' : `/${locale}`;

  return {
    alternates: {
      canonical: `${BASE_URL}${prefix}${path}`,
    },
  };
}
```

---

## 4. Hreflang tags

Hreflang tags are the most important multilingual SEO signal.
They tell search engines: "this page exists in these other languages, and here are the URLs."

Without hreflang, Google has to guess which language version to show in search results — and it often gets it wrong.

```html
<link rel="alternate" hreflang="en" href="https://example.com/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />
<link rel="alternate" hreflang="es" href="https://example.com/es/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />
```

The `x-default` tag tells search engines which URL to show when none of the specified languages match the user.

In Next.js, you can add hreflang through the metadata API's `alternates.languages` property.
Here's a reusable helper that works with gt-next:

```tsx title="lib/i18n-metadata.ts"
import { getDefaultLocale } from 'gt-next';
import { getLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';
const SUPPORTED_LOCALES = ['en', 'fr', 'es'];

export async function getI18NMetadata(path: string) {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();

  const getUrl = (loc: string) => {
    const prefix = loc === defaultLocale ? '' : `/${loc}`;
    return `${BASE_URL}${prefix}${path}`;
  };

  const languages: Record<string, string> = {};
  for (const loc of SUPPORTED_LOCALES) {
    languages[loc] = getUrl(loc);
  }
  languages['x-default'] = getUrl(defaultLocale);

  return {
    alternates: {
      canonical: getUrl(locale),
      languages,
    },
  };
}
```

Then use it in any page:

```tsx title="app/[locale]/about/page.tsx"
import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  return await getI18NMetadata('/about');
}
```

This generates both the canonical and all hreflang tags in one call.

---

## 5. Translated metadata

Search engines display your page title and description in results.
If these are in English for a French page, users are less likely to click — and Google may demote the result.

Use gt-next's [`getGT`](/docs/react/nextjs/reference/functions/get-gt) function to translate metadata strings:

```tsx title="app/[locale]/about/page.tsx"
import { getGT } from 'gt-next/server';
import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  const t = await getGT();
  const i18nMeta = await getI18NMetadata('/about');

  return {
    title: t('About Us'),
    description: t('Learn about our mission and team.'),
    ...i18nMeta,
  };
}
```

This gives you localized titles and descriptions in search results,
which significantly improves click-through rates for non-English queries.

---

## 6. Multilingual sitemaps

A sitemap helps search engines discover all your pages, including every language version.
For multilingual sites, you should include hreflang annotations in your sitemap as well.

Next.js supports [programmatic sitemaps](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap) through a `sitemap.ts` file:

```ts title="app/sitemap.ts"
import { MetadataRoute } from 'next';

const BASE_URL = 'https://example.com';
const LOCALES = ['en', 'fr', 'es'];
const DEFAULT_LOCALE = 'en';
const PAGES = ['/', '/about', '/blog', '/contact'];

export default function sitemap(): MetadataRoute.Sitemap {
  return PAGES.flatMap((path) => {
    const getUrl = (locale: string) => {
      const prefix = locale === DEFAULT_LOCALE ? '' : `/${locale}`;
      return `${BASE_URL}${prefix}${path === '/' ? '' : path}`;
    };

    const languages: Record<string, string> = {};
    for (const locale of LOCALES) {
      languages[locale] = getUrl(locale);
    }

    return LOCALES.map((locale) => ({
      url: getUrl(locale),
      lastModified: new Date(),
      alternates: { languages },
    }));
  });
}
```

This generates a sitemap with one entry per page per locale,
and each entry includes hreflang annotations pointing to all language versions.

---

## Checklist

Here's a quick summary of everything covered:

| SEO requirement         | Implementation                                                      |
| ----------------------- | ------------------------------------------------------------------- |
| Locale in URL           | [`createNextMiddleware()`](/docs/react/nextjs/reference/functions/create-next-middleware) with `[locale]` dynamic segment            |
| HTML `lang` attribute   | [`useLocale()`](/docs/react/reference/hooks/use-locale) in root layout                                        |
| Canonical URLs          | [`getLocale()`](/docs/react/nextjs/reference/functions/get-locale) + Next.js metadata API `alternates.canonical`         |
| Hreflang tags           | Next.js metadata API `alternates.languages` with all supported locales |
| Translated metadata     | [`getGT()`](/docs/react/nextjs/reference/functions/get-gt) for page titles and descriptions                         |
| Multilingual sitemap    | `sitemap.ts` with per-locale entries and hreflang alternates        |

---

Use the [`gt-next` Quickstart](/docs/react/nextjs-quickstart) to set up the full internationalization stack. Then configure routing with the [middleware guide](/docs/react/nextjs/app-router-middleware), generate multilingual pages with [static site generation](/docs/react/nextjs/app-router-static-site-generation), and add [right-to-left support](/docs/react/guides/managing-locales).

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/kb/sitemap.md) for every article.
