amannn / next-intl

🌐 Internationalization (i18n) for Next.js
https://next-intl-docs.vercel.app
MIT License
2.45k stars 221 forks source link

With LocalePrefix: 'never' locale switches to previous choice on navigating app pages #786

Open elensky opened 9 months ago

elensky commented 9 months ago

Description

While I used LocalePrefix: 'never' configuration I faced strange behaviour - if I navigate to any page of my app then change locale I see that locale was changed to previous choice when I navigate to other pages. I created CodeSandbox example of the issue, but looks like LocalePrefix: 'never' is not working there (I added a link to show this), you can download sources to run it locally. Addition to this issue (it might be helpful): If I change locale several times on the third time it stops changing NEXT_LOCALE cookie value, but it works well only with LocalePrefix: 'always'

Mandatory reproduction URL (CodeSandbox or GitHub repository)

https://codesandbox.io/p/devbox/next-intl-bug-template-app-forked-r7cscn

Reproduction description

Steps to reproduce:

  1. Open link and download sources
  2. Install dependencies (npm install) and run dev server (npm run dev)
  3. Open http://localhost:3000/ in browser - it opens /home page
  4. Click on the Page link - it opens '/page' page
  5. Click Switch to German link
  6. Click on the 'Home' link - page should be in German
  7. Click on the Page link - there are two unexpected results: page will be in English or application crashes with text on the screen: Application error: a client-side exception has occurred and in console there is this log:
    Warning: Cannot update a component (`HotReload`) while rendering a different component (`Router`). To locate the bad setState() call inside `Router`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
    ...
    Uncaught Error: SEGMENT MISMATCH
    ...
    The above error occurred in the <Router> component:
    ...

Expected behaviour

Locale and translations should stay in German

amannn commented 9 months ago

Hey, thanks for the report!

I tried reproducing it but no luck unfortunately yet:

https://github.com/amannn/next-intl/assets/4038316/c1743dac-9dfb-479d-973c-634f1f2c9cba

Am I doing something wrong here?

amannn commented 9 months ago

Btw. in the lockfile of your repro is a slightly outdated version of next-intl. I couldn’t reproduce the issue after updating, but maybe you want to upgrade nonetheless in case your app is on an older version currently.

IMG_9249

elensky commented 9 months ago

Looks like you are doing the same, but for me it reproduces every time.

https://github.com/amannn/next-intl/assets/8483591/293880d3-ec68-472b-9acd-f4109bdcc045

I've tried to use different versions and firstly face this behaviour with latest version, I experimented that why maybe it has old version in package lock. My idea was that it can work on old version because when I run it with version 3.0.1 first time it seems worked as on your video, but after I tried find a version it start working so on all the version I've tried including 3.0.1.

I even tried to use different node version and next version. Issue is still there.

elensky commented 9 months ago

My local package-lock:

Screenshot 2024-01-18 at 10 41 39
amannn commented 9 months ago

Interesting, not sure why you're seeing a different result.

There is however something related I saw recently that could explain this bug.

Reproduction:

  1. Go to https://next-intl-example-next-13.vercel.app/en (NEXT_LOCALE cookie is set to en)
  2. Switch to German using the locale switcher in the top right corner: Vercel potentially returns a cached response from the CDN that lacks the set-cookie header for NEXT_LOCALE
  3. Switch back to English: Next.js doesn't even make a request due to the router cache, therefore the cookie value isn't touched (it will coincidentally match in this very case)

(2) is a problem specific to Vercel. It also affects new visits, so e.g. if you have NEXT_LOCALE=en and you request /de, Vercel will potentially return a cached response for /de that doesn't include set-cookie for NEXT_LOCALE=en. This is less problematic when using a locale prefix, but would for example break an app that relies on cookies being accurate (such as when you use localePrefix: 'never'). It's interesting though that the Vercel docs state that a set-cookie header will cause a response to not be cached. Cloudflare has similar rules (haven't tried it yet). Edit: I've improved the docs in regard to this in https://github.com/amannn/next-intl/commit/353c49df3f119631cedd2f9a8b7ef8cee9230fe7.

(3) is a problem specific to Next.js. Opting out of the router cache for navigations that change the locale would be ideal, but maybe we can update the cookie manually by writing to document.cookie when the locale is changed via next-intl navigation APIs (i.e. <Link locale={…} />, useRouter().push(…, {locale: …}), useRouter().replace(…, {locale: …}).

I think addressing (3) could help with your use case.

amannn commented 9 months ago

I made some progress in regard to this in https://github.com/amannn/next-intl/pull/790. After hitting a blocker, I wanted to submit a bug report to Next.js, but found that the latest canary version of Next.js seems to improve the story here.

You can see my reproduction here: https://github.com/amannn/nextjs-bug-router-cache. After upgrading to the latest canary the buggy behavior was gone.

I think we should wait for the next stable release of Next.js and then continue looking into this.

Thanks again for the report!

amannn commented 9 months ago

So it turns out just a few hours after my comment Next.js 14.1 was released—which seems to fix the issue :).

https://github.com/amannn/next-intl/pull/790 furthermore includes a relevant fix for you. Updating both next and next-intl to the latest version should fix the error you were seeing!

Thanks again for the report!

elensky commented 9 months ago

Great thanks for your support and all work you are doing! I've checked - version 14.1 solved the issue. I am happy. Haven't expected that it will be solved so fast. Thanks again and good luck!

bim-oulabi commented 7 months ago

May I say that the issue of having wrong cookie under NEXT_LOCALE is still happening even after updating

for example if you visit '/en' the NEXT_LOCALE will be the previous one and so on

without having localePrefix sat to 'never'

amannn commented 7 months ago

@bim-oulabi Can you open a new issue with a reproduction?

bim-oulabi commented 7 months ago

I want to apologize as the issue was not because of next-intl,

To put it here in case anyone faced the issue in the future,

If you are using custom next/link to change locale

Disable prefetching by using prefetch={false}

As prefetch will override the locale cookie with the new locale

Or diasble default link behavior by using preventDefault()

And navigate using hooks

mxmzb commented 5 months ago

Adding prefetch={false} to the links that change the locale fixed this for me (on latest Next.js 14 hosted on Vercel)

beelzick commented 5 months ago

Newest Next.js version 14.2.3 breaks it down again, downgraded to Next.js 14.1.4 (with next-inl 3.13.0) and it works

amannn commented 5 months ago

Newest Next.js version 14.2.3 breaks it down again, downgraded to Next.js 14.1.4 (with next-inl 3.13.0) and it works

Seems like, there was a new report about this in https://github.com/amannn/next-intl/issues/1066. I'll reopen this for the time being until this is fixed on the Next.js side.

Does anyone know if there's an upstream bug report? If not, it might be a good idea to create one, maybe for the Next.js team to create a regression test.

Maybe as a workaround, you could call router.refresh() after a locale is changed to invalidate the Router cache:

Calling router.refresh will invalidate the Router Cache and make a new request to the server for the current route.

(source)

3li7u commented 5 months ago

Calling router.refresh will invalidate the Router Cache and make a new request to the server for the current route.

Calling router.refresh(); after changing the locale solves the problem for me.

const handleLocaleChange = (locale: string) => {
    startTransition(() => {
      router.replace(`${pathname}?${searchParams.toString()}`, { locale });
      router.refresh(); // clear caching to make sure the locale cookie is set correctly
    });
  };
mxmzb commented 4 months ago

Seems to be broken again, and adding prefetch={false} does not help any more.

ksin commented 4 months ago

Same issue here with prefetch={false} as well.

danieljpgo commented 4 months ago

I'll try other solutions, but I wouldn't want to remove prefetch, as it helps to have faster navigation between pages.

AhmedBaset commented 1 week ago

This is still an issue even with

experimental: {
    staleTimes: {
      static: 0,
      dynamic: 0,
    },
}

None of the mentioned workarounds worked.