Long TTFB on multilangual site. Prismic cache bug (probably)

Hi @Vladyslav.L, thank you for your patience. :slight_smile: 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:

  1. Install the negotiator and @formatjs/intl-localematcher packages:

    npm install negotiator @formatjs/intl-localematcher
    
  2. Create a i18n.js file at the root of your project (or within src 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;
        }
      }
    }
    
  3. 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).*)"],
    };
    
  4. Update the route resolver in prismicio.js to include :lang and/or custom locales. The following example matches the LOCALES record in i18n.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. :slight_smile:

1 Like