Internationalization (i18n) with Next.js and two domains

Which Router Are You Using?

Before proceeding, determine whether you’re using:

  1. Pages Router (pages/) – Uses Next.js’ built-in i18n config for locale detection.

  2. App Router (app/) – Requires middleware to determine locales based on domain.


For Pages Router (pages/)

If you’re using the Pages Router, Next.js automatically detects the locale based on the domain (no middleware required).

:one: Update next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
    i18n: {
        locales: ['en-us', 'fr-fr'],
        defaultLocale: 'en-us',
        domains: [
            {
                domain: 'website.com',
                defaultLocale: 'en-us',
            },
            {
                domain: 'website.fr',
                defaultLocale: 'fr-fr',
            }
        ]
    }
};

module.exports = nextConfig;

:white_check_mark: This automatically detects the locale from the domain and routes pages accordingly.

:two: Fetching Content in pages/[uid].tsx

Next.js provides the locale in context.locale. You can pass it to Prismic when fetching content:

import { GetServerSideProps } from 'next';
import { createClient } from '@/prismicio';
import { SliceZone } from '@prismicio/react';
import { components } from '@/slices';
import { notFound } from 'next/navigation';

type PageProps = {
    page: any;
};

export default function Page({ page }: PageProps) {
    return (
        <main>
            <SliceZone slices={page.data.slices} components={components} />
        </main>
    );
}

// ✅ Fetch the correct locale content from Prismic
export const getServerSideProps: GetServerSideProps = async ({ params, locale }) => {
    const client = createClient();
    const page = await client.getByUID('page', params?.uid as string, { lang: locale }).catch(() => null);

    if (!page) return { notFound: true };

    return { props: { page } };
};

:white_check_mark: Why This Works

• getServerSideProps automatically gets the locale from the request.
• Fetches the correct localized content from Prismic.
• No need for manual locale detection.

:three: Using Localized Links

When linking between pages, use the built-in locale system:

import Link from 'next/link';
import { useRouter } from 'next/router';

export default function Navigation() {
    const { locale } = useRouter();

    return (
        <nav>
            <Link href="/" locale="en-us">English</Link>
            <Link href="/" locale="fr-fr">Français</Link>
        </nav>
    );
}

:white_check_mark: Next.js will automatically switch domains when clicking the links.


For App Router (app/)

If you’re using the App Router, you need middleware to handle locales based on domains since next.config.js won’t do it.

:one: middleware.ts (Required)

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
    const host = request.nextUrl.host;

    let locale = 'en-us';
    if (host.endsWith('website.fr')) {
        locale = 'fr-fr';
    }

    request.headers.set('x-locale', locale);

    return NextResponse.next();
}

export const config = {
    matcher: '/((?!_next|static|favicon.ico).*)',
};

:white_check_mark: This ensures that every request gets the correct locale based on the domain.


:two: Fetching Content in app/page.tsx

Since the params object doesn’t include lang, retrieve it from headers:

import { headers } from 'next/headers';
import { createClient } from '@/prismicio';
import { SliceZone } from '@prismicio/react';
import { components } from '@/slices';
import { notFound } from 'next/navigation';

export default async function Page({ params }: { params: { uid: string } }) {
    const client = createClient();

    // ✅ Retrieve the locale set by middleware
    const locale = headers().get('x-locale') || 'en-us';

    const page = await client.getByUID('page', params.uid, { lang: locale }).catch(() => notFound());

    return (
        <main>
            <SliceZone slices={page.data.slices} components={components} />
        </main>
    );
}

:white_check_mark: The headers().get('x-locale') ensures the correct locale is used.