Inconsistent behavior of `usePathname` with static rendering during build
Link to the code that reproduces this issue
https://github.com/amannn/nextjs-bug-repro-usepathnamessg/commit/c6b084df43e08a9b043548fb577be8db5f059bde
To Reproduce
Compare the output of usePathname in development vs during the static prerender when accessing the route /test which uses a rewrite in the middleware.
Current vs. Expected behavior
usePathname seems to pick up a request-time pathname from a middleware rewrite. However, as this information is not available during a static build, usePathname will return a different value in this case. Then again, when running the app in production, usePathname will return the pathname that's displayed in the browser (i.e. after a rewrite), but only on the client side.
In the reproduction this leads to a text content mismatch, but more generally this breaks a dev/prod guarantee. I'd expect a static prerender to only be a performance optimization and not to change application logic.
I guess some unification would be necessary here.
Note that this only applies for static prerendering, if you use SSR in production then the behavior is the same as in dev.
A more real world use case where this behavior was encountered: https://github.com/amannn/next-intl/issues/1568
Provide environment information
Operating System:
Platform: darwin
Arch: x64
Version: Darwin Kernel Version 23.6.0: Mon Jul 29 21:13:00 PDT 2024; root:xnu-10063.141.2~1/RELEASE_X86_64
Available memory (MB): 16384
Available CPU cores: 12
Binaries:
Node: 20.11.1
npm: 10.2.4
Yarn: 1.22.22
pnpm: 9.14.2
Relevant Packages:
next: 15.0.4-canary.23 // Latest available version is detected (15.0.4-canary.23).
eslint-config-next: 15.0.4-canary.23
react: 19.0.0-rc-380f5d67-20241113
react-dom: 19.0.0-rc-380f5d67-20241113
typescript: 5.6.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Middleware
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), next start (local)
Additional context
Maybe a hook like useInternalPathname() (naming TBD) could help that always reliably returns the pathname that renders in Next.js, regardless of any rewrites—essentially matching the directory structure in src/app.
Hey @amannn!
After talking to Vercel they clarified that the usePathname function with rewrites will cause hydration errors by default. It's mentioned under "good to know" here.
I suppose a workaround could be done in next-intls wrapped version of usePathname, where a .replace() is done on the pathname to get the correct value. Do you think this is viable?
Personally I think it'd be great if Next.js could handle this sometime in the future, since having hydration errors "by default" isn't great UX, especially when it only shows up in the built application.
Also @amannn let me know if it sounds like a good idea and you want me to create a PR for it!
Hey, thanks for chiming in here @hugotiger!
Right, I didn't notice that there's even docs on this behavior.
I did add a workaround in https://github.com/amannn/next-intl/pull/1578. I guess you still have a case where there's an issue? Can you share more on it? A failing test case is of course also a great option and we can see where to go from there.
Oh interesting @amannn! I'll try to create a minimal reproduction or a failing test case.
What's the most suitable example app to add the test case? It needs to use static rendering and I had a hard time understanding where I could set up that test case 🤔 If you could guide me in the right direction would be awesome!
I think the easiest might be to create an isolated reproduction with https://github.com/amannn/next-intl-bug-repro-app-router.
If we can reproduce it there, we can set up a unit test similar to what I did in https://github.com/amannn/next-intl/pull/1578. It depends a bit on mocking, so ensuring this can be reproduced in an e2e fashion is certainly a good first step!
I set up a reproduction now and opened an issue in the next-intl repo instead: https://github.com/amannn/next-intl/issues/2011
This bug also seems to occur with this pattern:
export const metadata: Metadata = {
metadataBase: "http://localhost:3000",
alternates: {
canonical: "./",
},
};
See https://github.com/amannn/next-intl/issues/2119
Maybe useRoute could be helpful to address this: https://github.com/vercel/next.js/pull/85527