Hi @Vladyslav.L, thank you for your patience. We have an update.
As you discovered, websites supporting multiple locales using createLocaleRedirect
from @prismicio/next
always have a 200ms-500ms TTFB delay.
We found this happens because createLocaleRedirect
fetches all of a Prismic repository's locales. The list of locales is used to detect and, if needed, prefix a locale to the URL.
Unfortunately, fetch
calls made in middleware.js
are not cached like calls in your website's pages. As a result, a request to fetch all of the Prismic repository's locales is made on every page load. This was an oversight in our testing when we created the createLocaleRedirect
helper.
Going forward, we believe the best solution is to manage locales directly within a website's codebase. Such a solution will require maintaining a list of locales, but it will be much more performant than making an extra uncached network request on each page.
We are working on updating our documentation and multi-language starter with new guidelines. We will be deprecating @prismicio/next
's createLocaleRedirect
.
In the meantime, here is a draft version of our latest recommendation:
-
Install the
negotiator
and@formatjs/intl-localematcher
packages:npm install negotiator @formatjs/intl-localematcher
-
Create a
i18n.js
file at the root of your project (or withinsrc
if you use that directory):import Negotiator from "negotiator"; import { match } from "@formatjs/intl-localematcher"; /** * A record of locales mapped to a version displayed in URLs. The first entry is * used as the default locale. */ const LOCALES = { "en-us": "en", "fr-fr": "fr", }; /** * Creates a redirect with an auto-detected locale prepended to the URL. * * @param request {import("next/server").NextRequest} * @returns {Response} */ export function createLocaleRedirect(request) { const headers = { "accept-language": request.headers.get("accept-language") }; const languages = new Negotiator({ headers }).languages(); const locales = Object.keys(LOCALES); const locale = match(languages, locales, locales[0]); request.nextUrl.pathname = `/${LOCALES[locale]}${request.nextUrl.pathname}`; return Response.redirect(request.nextUrl); } /** * Determines if a pathname has a locale as its first segment. * * @param request {import("next/server").NextRequest} * @returns {boolean} */ export function pathnameHasLocale(request) { const regexp = new RegExp(`^/(${Object.values(LOCALES).join("|")})(\/|$)`); return regexp.test(request.nextUrl.pathname); } /** * Returns the full locale of a given locale. It returns `undefined` if the * locale is not in the master list. * * @param locale {string} * @returns {string | undefined} */ export function reverseLocaleLookup(locale) { for (const key in LOCALES) { if (LOCALES[key] === locale) { return key; } } }
-
Create or modify your
middleware.js
file with the following:import { createLocaleRedirect, pathnameHasLocale } from "@/i18n"; /** * @param request {import("next/server").NextRequest} */ export async function middleware(request) { if (!pathnameHasLocale(request)) { return createLocaleRedirect(request); } } export const config = { matcher: ["/((?!_next|api|slice-simulator|icon.svg).*)"], };
-
Update the route resolver in
prismicio.js
to include:lang
and/or custom locales. The following example matches theLOCALES
record ini18n.js
.export const routes = [ { type: "page", path: "/:lang/:uid" }, { type: "page", lang: "en-us", path: "/en/:uid" }, { type: "page", lang: "fr-fr", path: "/fr/:uid" }, // Treat the `home` UID as the root route. { type: "page", uid: "home", path: "/:lang" }, { type: "page", uid: "home", lang: "en-us", path: "/en" }, { type: "page", uid: "home", lang: "fr-fr", path: "/fr" }, ];
If any of that is unclear, please let me know and I'll try to explain it.