'Redirect based on header OR cookie in Next.js next.config.js

We're using Next.js and want to route all paths (not just root) to locale-based paths based on the browser Accept-Language header. However, if the user SETS their region, we will set a cookie that would need to be checked first to respect user preferences.

So we need to check for the cookie, and if it's not there, try redirect based on browser language header instead. We're using ISG so limited to next.config.js redirects serverside.

According to the docs, this should work, but since we're using ISG, we need to do this in next.config.js redirects function.

We've tried this solution and it does not work (we get infinite redirects as both cookie AND header match):

const { i18n } = require('./next-i18next.config');
const withTM = require('next-transpile-modules')(['fitty', 'react-svg']); // pass the modules you would like to see transpiled

const handleLocaleRedirects = (path) => {
  const result = [];
  i18n.locales.forEach((locale) => {
    i18n.locales.forEach((loc) => {
      if (loc !== locale) {
        result.push({
          source: `/${locale}${path}`,
          has: [
            {
              type: 'header',
              key: 'accept-language',
              value: `^${loc}(.*)`,
            },
          ],
          permanent: false,
          locale: false,
          destination: `/${loc}${path}`,
        });
        result.push({
          source: `/${locale}${path}`,
          has: [
            {
              type: 'cookie',
              key: 'NEXT_LOCALE',
              value: loc,
            },
          ],
          permanent: true,
          locale: false,
          destination: `/${loc}${path}`,
        });
      }
    });
  });
  return result;
};

module.exports = withTM({
  i18n,
  reactStrictMode: true,
  images: {
    domains: [
      'dxjnh2froe2ec.cloudfront.net',
      'starsona-stb-usea1.s3.amazonaws.com',
    ],
  },
  eslint: {
    // Warning: Dangerously allow production builds to successfully complete even if
    // your project has ESLint errors.
    ignoreDuringBuilds: true,
  },
  async redirects() {
    return [...handleLocaleRedirects('/:celebrityId')];
  },
});


Solution 1:[1]

I've managed to achieve this using _app.js
Add getInitialProps inside _app.js
It checks cookie inside request, gets current locale using ctx.locale, My default locale is en-IN so if targetLocale matches default locale it sets an empty string to targetLocale, then redirects using header.
Other than that we don't have to use localeDetection because we are handling on our own.

MyApp.getInitialProps = async ({ ctx }) => {
  if (ctx.req) {
    const rawCookies = ctx.req.headers.cookie
    let locale = ctx.locale
    const path = ctx.asPath

    if (rawCookies != undefined) {
      const cookies = cookie.parse(rawCookies)
      let targetLocale = cookies['NEXT_LOCALE']

      if (targetLocale != locale) {
        if (targetLocale == 'en-IN') {
          targetLocale = ''
        } else {
          targetLocale = '/' + targetLocale
        }

        ctx.res.writeHead(302, {
          Location: `${targetLocale}${path}`
        })
        ctx.res.end()
      }
    }
  }

  return {}
}

Other than this, I'm showing modal when there is no cookie named NEXT_LOCALE to handle first time users.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sølve Tornøe