next-i18next: Error: appWithTranslation was called without a next-i18next config

Describe the bug

Error: appWithTranslation was called without a next-i18next config

Occurs in next-i18next version

I tried the new version - 8.0.1

Steps to reproduce

  • next-i18next.config.js module.exports = { i18n: { defaultLocale: 'en_US', locales: ['en_US', 'de_DE', 'fr_CH'], }, }

  • next.config.js const { i18n } = require(‘./next-18next.config’)

module.exports = { i18n, }`

  • _app.js import { appWithTranslation } from ‘next-i18next’; import ‘…/styles/style.scss’; import ‘swiper/swiper-bundle.css’; import ‘swiper/components/navigation/navigation.scss’; import ‘react-calendar/dist/Calendar.css’;

const MyApp = ({ Component, pageProps }) => <Component {…pageProps} />

export default appWithTranslation(MyApp)

Screenshots

image

OS (please complete the following information)

  • Device: Ubuntu 20.04 LTS
  • Browser: Chrome 88.0.4324.182

Additional context

Add any other context about the problem here.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 14
  • Comments: 30 (8 by maintainers)

Most upvoted comments

Adding config to serverSideTranslations fixed it for me. ...(await serverSideTranslations(locale, ['common'], nextI18NextConfig)),

@BenAllenUK had the right approach. I ended up tracing through the same issue because I didn’t see the notes until after I was done 😃

Overview

I have a hand-crufted fix in hand, deployed in a real app, details in next section. I do not know how to make this as a patch that we can ship in the library that will work in both non-bundled and bundled cases with the module at the root of a monorepo or in a child package directory. I’m willing to do a Google Meet or other brainstorming / pairing session with anyone that has an idea to try out.

and I believe we can fix this by changing to await import to prevent Webpack from pointing the config file import to the empty dummy using the technique for how next.config.js is avoiding this problem (turns out the next.config.js approach only works because next is external / not bundled).

I am NOT using target: serverless (using this with Next.js 13.1.1) but that does not matter. I have a replacement for target: serverless that I’ll be sharing shortly that is even better but does the same bundling of node_modules to prevent enormous deployments and slow starts on Lambda. The solution below will apply go target: serverless though as the problem is just Webpack.

The hand-crufted Fix

  • Open node_modules/next-i18next/dist/commonjs/serverSideTranslations.js
  • Find the line return Promise.resolve("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH))).then(function (s) {
  • Replace that entire statement (it’s 3 lines) with the block below
  • Add the webpack config to the next.config.js from below - This will cause the contents of the file to be bundled (so editing the next-i18next.config.js file at runtime will have no effect)

next.config.js Block

const path = require('path');

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  webpack: (config, options) => {
    const { dev, isServer } = options;
    if (isServer && config.name === 'server' && !dev) {
      const contextReplacePlugin = new options.webpack.ContextReplacementPlugin(/next-i18next/, './', {
        // Left side (./next-i18next.config.js) is the path that require is looking for at build time
        // Right side (path.resolve(./next-i18next.config.js)) is the path to the file that will be
        // bundled into the server bundle and used at runtime
        './next-i18next.config.js': path.resolve('./next-i18next.config.js'),
      });

      // Put at front of plugins array
      config.plugins.unshift(contextReplacePlugin);
    }

    return config;
  },
};

node_modules/next-i18next/dist/commonjs/serverSideTranslations.js Block

// Make this require no longer an expression so the path stays the same
// for Webpack - This allows us to fix the path with ContextReplacementPlugin
// When the path is wrapped with `path.resolve` it makes it compute one value
// at build time and then a different value at runtime so the `map` that is built
// to find the name at runtime has an entry that does not match the item being
// looked for.  It's possible to hand-fix this map by placing the fully qualified
// path to the file into the map and pointing to the same module number to prove
// that the issue is the difference in paths between build time and runtime.
return _interopRequireWildcard(require(DEFAULT_CONFIG_PATH));

Problem Summary

  • node_modules/next-i18next/dist/commonjs/serverSideTranslations.js requires the config file via a expression (path.resolve primarily), resolved as a promise, which then evaluates another expression (path.resolve again)
  • Webpack doesn’t evaluate these expressions when bundling so it just points the require to the empty module and fails at runtime
  • There is a gotcha where trying changes locally will not actually change the output .next/server/chunks, making it very frustrating / misleading to try to fix this
  • Relative imports of ../**/next-i18next.config in application code work fine because they are not an expression and webpack can find them at build time
  • Because Webpack can’t figure out the path in the require it will not be able to follow any directives in config.externals such as { 'next-i18next.config': './next-i18next.config.js' } - The problem is not that the file is not externalled but rather that Webpack has no idea what this require is so it just points it to empty

The Fix

The path.resolve call from DEFAULT_CONFIG_PATH has to be removed as a temporary workaround.

Now… why does this work for the next.js import of next.config.js? It appears that using await import(...) is either a workaround or a legit way to say that we want this imported at runtime, in any case it appears that it stops Webpack from messing this up while still allowing discovery of the path at runtime. Correction: this works for next because next is not bundled so the import expression is evaluated only at runtime.

Applying the Fix Locally

  • Install patch-package
  • Add postinstall script that runs patch-package
    • "postinstall": "patch-package --patch-dir ./patches/"
  • Add the contents of the path below to patches/next-i18next+13.0.3.patch
  • Run npm i or yarn install again to run patch-package
  • If the patch fails to apply because of a version mis-match, try running npx patch-package next-i18next to regenerate the patch - if that fails, apply the patch by hand-editing the file (make sure you get the commonjs version), then run npx patch-package next-i18next to regenerate the patch
  • Validate that next-i18next is installed in the node_modules directory that is in the same directory as next-i18next.config.js - If these are different (e.g. next-i18next is a child of another module) then you’ll have to edit the number of ../ in the path
  • 👉 rm -rf .next/cache or rm -rf .next
  • Rebuild next
  • Make sure to actually ship next-i18next.config.js if deploying
  • Run your server
  • 🎉

patches/next-i18next+13.0.3.patch Patch File Contents

diff --git a/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js b/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js
index 67dc9af..fc5b765 100644
--- a/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js
+++ b/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js
@@ -79,9 +79,7 @@ var serverSideTranslations = /*#__PURE__*/function () {
               break;
             }
             _context.next = 9;
-            return Promise.resolve("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH))).then(function (s) {
-              return _interopRequireWildcard(require(s));
-            });
+              return require('../../../../next-i18next.config.js');
           case 9:
             userConfig = _context.sent;
           case 10:

The Line in next-i18next that Breaks Webpack

https://github.com/i18next/next-i18next/blob/6a692ae688d4cb5c95bc8ff0a378f12a6a2fe61c/src/serverSideTranslations.ts#L37

How Next.js Imports the next.config.js at Runtime

This works for next when next is not bundled. But this fails just the same if used in next-i18next as it still uses an expression for the import.

https://github.com/vercel/next.js/blob/70c087e4cf6188d5290b1fe32975b22e17b03b69/packages/next/src/server/config.ts#L922

👉 Why No One Has Confirmed a Fix for This 👈

Locally patching node_modules/next-i18next/dist/commonjs/serverSideTranslations.js and re-running next build will show no change, driving you mad.

The problem is that the node_modules are only compiled once and cached in .next/cache.

If you make a change to a file in node_modules you have to rm -rf .next/cache before you build again, then you will see that the resulting chunk file has your changes.

next-i18next Require of next-i18next.config.js is an Expression

            return Promise.resolve("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH))).then(function (s) {
              return require("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH));
            });

TIL that it is crucial that the config file looks exactly like the one described in the README. I was getting this exact error scratching my head as to why it’s missing, then I realized my config file looks like this:

// next-i18next.config.js
module.exports = {
  locales: ['en', 'hu'],
  defaultLocale: 'en',
};

// next.config.js
const i18n = require('./next-i18next.config.js');
module.exports = { i18n };

instead of this:

// next-i18next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'hu'],
    defaultLocale: 'en',
  },
};

// next.config.js
const { i18n } = require('./next-i18next.config.js');
module.exports = { i18n };

I hadn’t realized that was a load-bearing i18n key

I got the same error and for me the problem was that I use the serializeConfig: false, option. Once I added the configuration manually to appWithTranslation like this appWithTranslation(MyApp, nextI18NextConfig) it worked. I realize it is not the same config as the reporter’s one, but it may help someone.

Interesting, thanks for a very detailed account.

I think what you’re suggesting would involve changing this import to a regular require. Do you want to give that a test, and see if it solves the issue?

Nothing here worked for me. Seriously!

I had the same error and for me was the case was that I was not sending the correct props down to the pages.

I was sending in an object containing the key _nextI18Next :

// this is wrong
{ 
  _nextI18Next: { 
   initialI18nStore: [Object],
   initialLocale: 'en',
   userConfig: [Object]
 }

instead of the correct object:

// this is expected
{ 
   initialI18nStore: [Object],
   initialLocale: 'en',
   userConfig: [Object]
 }

I was not spreading the ...serverSideTranslations and got confused that was the expected props.

Hopefully, this will help someone.

Hi @hectorstudio – unfortunately I’m unable to determine what is going wrong based on the snippets you’ve copy/pasted. Can you please provide a full repo?

Also just a heads up, regional locales use dashes, not underscores. A correct locale would be en-US.