Internationalization (i18n) with Next.js and two domains

Hi,

I am looking for a solution using Prismic and Next.js that allows me to support two languages, such as English and French, while using the same templates and codebase. The language should be determined by the domain name: website.com for English and website.fr for French.

I had a look at the guide here https://prismic.io/blog/nextjs-i18n on how to enable internationalization, but it doesnät explain how it can be used with two domains, normal URL structure, default locale and how to fetch the correct content for the page with Prismic.

My next config looks like this:

const nextConfig = {

    i18n: {

        locales: ['en-us', 'fr-fr'],
        defaultLocale: 'en-us',

        domains: [
            {
                domain: 'website.com',
                defaultLocale: 'en-us',
            },
            {
                domain: 'website.fr',
                defaultLocale: 'fr-fr',
            }
        ]
    }
}

But I am not sure what changes I need to do in my middleware.ts file function or in page.tsx to fetch the correct content from Prismic based on the language used for the domain.

export async function middleware(request: NextRequest) {

    const client = createClient()
    const repository = await client.getRepository()

    const locales = repository.languages.map((lang) => lang.id)

    ???
}

Then lastly what needs to be added to the page template to get the locales and fetch content. I am not sure how I can get the params.lang on page level, it doesn't exist on my Prismic setup.

type Params = { uid: string, lang: string }

export default async function Page({ params }: { params: Params }) {
	
    let page

    const client = createClient()

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

    ???

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

It would be appriciated if anyone can give me some example how this can be achieved.

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.

Hi Phil,

Thanks for the quick response and detailed answer. I haven’t tried it in practice yet but will go over it later. It looks like what I want to achieve: I have two domains, one for each language, and I’d like to use the same Prismic repository for my content.

My route resolver looks like this.

const routes: prismic.ClientConfig["routes"] = [
	{
		type: "home",
		path: "/",
	},
	{
		type: "page",
		path: "/:uid",
	},
	{
		type: "article",
		path: "/:uid",
	},
	{
		type: "showcase",
		path: "/work/:uid",
	}
]

Best,
Richard

1 Like

Hi again Phil,

I tried your suggestion for an app router setup, and it worked seamlessly as described. It’s a simple and elegant solution. Now, I just need to apply it to all objects and see if I can get the entire web solution to run localization based on the domain name.

Thanks again!

2 Likes