How can SSG+i18n+dynamic routes be managed in NextJS ^14, strictly using the pages router?

138 views Asked by At

In an effort to facilitate the nextjs upgrade (12 -> 14), it's become unclear how to configure i18n in an SSG app only using dynamic catch all routes leveraging the pages router.

The crux of the problem is NextJS is now erroring for SSG when setting i18n in config. When it's removed and locales are side loaded in getStaticPaths, the build errors out indicating locale is not specified in config.

Can someone please point to the changes necessary to facilitate this upgrade? next-translate is currently used to handle i18n but is not a requirement to maintain; SSG, i18n, the pages router and dynamic catch all routes are.

next.config.js:

const nextConfig = {
  output: 'export',
  i18n: {
    locales, defaultLocale,
    localeDetection: false,
  },

./src/pages/[[...slug]].js (dynamic catch all route):

export const getStaticPaths = async ({ locales }) => {
  const paths = [];
  await Promise.all(locales?.map(async (locale) => {
    // fetch and parse entries, then:
    paths.push({ params: { slug }, locale });
  }));

  return {
    paths,
    fallback: false
  };
};

i18n.js:

module.exports={
  "localeDetection": false,
  "locales": [
    "en-US",
    "es"
  ],
  "defaultLocale": "en-US",
  "loadLocaleFrom": (lang, ns) => import(`./locales/${lang}/${ns}.json`).then((m) => m.default),
  "pages": {
    "*": [
      "common"
    ]
  },
  "logBuild": false
};

Versions:

  • node: 20.11.0
  • next: 14.1.0
  • react: 18.2.0
  • next-translate: 2.6.2

Given the previous/original next.config.js, the build fails, logging i18n cannot be specified in next.config.js if output: 'export' is also set. Yet, removing i18n surfaces issues w/in getStaticProps:

  1. locales does not exist in NextJS cache, given if no i18n is set in config. Previously, locales (from NextJS context) was mapped over as a part of paths generation, e.g. getStaticPaths snippet above.

  2. Attempting to side-load locales w/in getStaticPaths, e.g.:

import i18n from '../../i18n';
export const getStaticPaths = async () => {
  const { locales } = i18n;

Yields the following error:

Error: Invalid locale returned from getStaticPaths for /[[...slug]], the locale en-US is not specified in next.config.js
1

There are 1 answers

0
christopher.theagen On BEST ANSWER

Build is completing when using patch-package to comment out two errors relating to i18n use w/ SSG. In effect, this patch circumvents seemingly needless restrictions imposed by NextJS (at least on v13.x.x) to prevent i18n with static export.

next+13.4.9.patch

diff --git a/node_modules/next/dist/export/index.js b/node_modules/next/dist/export/index.js
index f0579c2..57dc01a 100644
--- a/node_modules/next/dist/export/index.js
+++ b/node_modules/next/dist/export/index.js
@@ -326,9 +326,9 @@ async function exportApp(dir, options, span) {
             };
         }
         const { i18n , images: { loader ="default" , unoptimized  }  } = nextConfig;
-        if (i18n && !options.buildExport) {
-            throw new ExportError(`i18n support is not compatible with next export. See here for more info on deploying: https://nextjs.org/docs/messages/export-no-custom-routes`);
-        }
+        // if (i18n && !options.buildExport) {
+        //     throw new ExportError(`i18n support is not compatible with next export. See here for more info on deploying: https://nextjs.org/docs/messages/export-no-custom-routes`);
+        // }
         if (!options.buildExport) {
             const { isNextImageImported  } = await nextExportSpan.traceChild("is-next-image-imported").traceAsyncFn(()=>_fs.promises.readFile((0, _path.join)(distDir, _constants1.EXPORT_MARKER), "utf8").then((text)=>JSON.parse(text)).catch(()=>({})));
             if (isNextImageImported && loader === "default" && !unoptimized && !_ciinfo.hasNextSupport) {
diff --git a/node_modules/next/dist/server/config.js b/node_modules/next/dist/server/config.js
index 90a4db3..dc40674 100644
--- a/node_modules/next/dist/server/config.js
+++ b/node_modules/next/dist/server/config.js
@@ -211,9 +211,9 @@ function assignDefaults(dir, userConfig, silent = false) {
         ...config
     };
     if (result.output === "export") {
-        if (result.i18n) {
-            throw new Error('Specified "i18n" cannot be used with "output: export". See more info here: https://nextjs.org/docs/messages/export-no-i18n');
-        }
+        // if (result.i18n) {
+        //     throw new Error('Specified "i18n" cannot be used with "output: export". See more info here: https://nextjs.org/docs/messages/export-no-i18n');
+        // }
         if (result.rewrites) {
             throw new Error('Specified "rewrites" cannot be used with "output: export". See more info here: https://nextjs.org/docs/messages/export-no-custom-routes');
         }

next.config.js:

require('dotenv').config();
const withPlugins = require('next-compose-plugins');
const nextTranslate = require('next-translate');

const i18n = require('./i18n');

const { locales, defaultLocale } = i18n;

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  i18n: { locales, defaultLocale },
};

module.exports = withPlugins(
  [
    [
      {
        generateBuildId: async () => 'build',
      }
    ],
    nextTranslate
  ],
  nextConfig
);

Versions used in current working build (changed from those specified in the question):

  • next: 13.4.9
  • next-translate: 1.0.7