react-router
react-router copied to clipboard
"{hook} may be used only in the context of a <Router> component" when running with vitest, node >=22.12 and there's a peer-dep on react-router
I'm using React Router as a...
library
Reproduction
I created the minimum steps to reproduce in this repository: https://github.com/acelaya/peer-react-router-vitest
The readme includes the steps to reproduce the issue.
System Info
System:
OS: Linux 6.8 Ubuntu 24.04.1 LTS 24.04.1 LTS (Noble Numbat)
CPU: (16) x64 11th Gen Intel(R) Core(TM) i7-11700K @ 3.60GHz
Memory: 50.17 GB / 62.68 GB
Container: Yes
Shell: 5.9 - /usr/bin/zsh
Binaries:
Node: 22.13.0 - ~/.nvm/versions/node/v22.13.0/bin/node
npm: 10.9.2 - ~/.nvm/versions/node/v22.13.0/bin/npm
Browsers:
Chrome: 131.0.6778.264
npmPackages:
react-router: ^7.1.3 => 7.1.3
vite: ^6.0.5 => 6.0.8
Used Package Manager
npm
Expected Behavior
It should be possible to depend on packages that have a peer dependency on react-router, and have no errors when running with vitest.
Actual Behavior
In short, if your project depends on react-router and a dependency which in turn has a peer-dependency on react router, when running with vitests, all imports from react-router inside that dependency will resolve a different instance than the ones in the root project, causing errors like useLocation() may be used only in the context of a <Router> component.
This issue was reported in https://github.com/remix-run/react-router/issues/12475, but it was then closed as a solution was provided that solved it for other use cases, but the error still exists when running with vitest.
It also only affects node 22.12 and newer. Earlier versions work as expected. This can be seen in this GitHub workflow execution, from the repro repository above: https://github.com/acelaya/peer-react-router-vitest/actions/runs/12864662838
I am facing the same issues after replacing react-router-dom with react-router v7.
This issue is present for me when using tsx to run a custom server in dev mode. When building and running in production, the issue is not present.
I've experimented with node 22.12 and 22.13, as well as vite 5 and vite 6. The issue persists across all of them. Using react-router 7.1.3.
The external package that I am using is @rvf/react-router
Removing module-sync in node_modules/react-router/package.json does resolve the issue for me.
Removing module-sync in node_modules/react-router/package.json does resolve the issue for me.
True! I forgot to mention that in the description.
That's indeed what fixed it for me in the original issue https://github.com/remix-run/react-router/issues/12475#issuecomment-2578346227
I am also facing the same issues with React Router v7 and Vite when using the new Clerk React Router SDK. Even when I use Node version 22.11 or lower, as well as Vite 5 and 6, I get this error message:
I'm using these dependencies:
"dependencies": {
"@chakra-ui/react": "^3.1.0",
"@clerk/react-router": "^0.2.1",
"@emotion/react": "^11.13.3",
"@reduxjs/toolkit": "^2.3.0",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-avatar-editor": "^13.0.2",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.10",
"react-headroom": "^3.2.1",
"react-icons": "^5.3.0",
"react-redux": "^9.1.2",
"react-router": "^7.1.3",
"react-use": "^17.5.1",
"redux-logger": "^3.0.6"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"vite": "^5.4.10",
"vite-plugin-env": "^1.0.1"
}
@justinwaite Do you mean i need to remove the module-sync line in node_modules/react-router/package.json ?
"exports": {
".": {
"node": {
"types": "./dist/development/index.d.ts",
"module-sync": "./dist/development/index.mjs", <--------------- Remove this line
"default": "./dist/development/index.js"
},
"import": {
"types": "./dist/development/index.d.mts",
"default": "./dist/development/index.mjs"
},
"default": {
"types": "./dist/development/index.d.ts",
"default": "./dist/development/index.js"
}
},
"./route-module": {
"import": {
"types": "./dist/development/lib/types/route-module.d.mts"
},
"default": {
"types": "./dist/development/lib/types/route-module.d.ts"
}
},
"./dom": {
"node": {
"types": "./dist/development/dom-export.d.ts",
"module-sync": "./dist/development/dom-export.mjs", <--------------- Remove this line
"default": "./dist/development/dom-export.js"
},
"import": {
"types": "./dist/development/dom-export.d.mts",
"default": "./dist/development/dom-export.mjs"
},
"default": {
"types": "./dist/development/dom-export.d.ts",
"default": "./dist/development/dom-export.js"
}
},
"./package.json": "./package.json"
},
And then delete the cache?
rm -rf node_modules/.vite
@MichaelvdVeer based on your description, it seems to me you are facing a different scenario. Perhaps you are in fact using the hook outside of a router.
Hi @acelaya,
Thanks for your reply!
I am using the ClerkProvider in main.jsx and then using createBrowserRouter in a separate file (AppRoutes.jsx). I’ve created a minimal reproduction of the issue, which you can find here: https://github.com/MichaelvdVeer/clerk-react-router-example.
Could you take a look at this and let me know if I’m missing something?
Thanks in advance! If needed, I will create a separate issue.
If needed, I will create a separate issue.
Yes, please. Let's not derail the topic here.
My issue is very similar to this one, with some notable differences:
- Happens on Node 20
- Removing the dependency that has a peer dependency on react-router doesn't fix the problem
Removing module-sync in node_modules/react-router/package.json does resolve the issue for me.
This seems to work for me too! However, I don't see this happening with the peer dependency importing stuff - I'm importing from our own src/sdk.jsx in src/sdk.spec.jsx and this doesn't (shouldn't) hit the storybook addon at all.
edit - nope, doesn't seem to workaround the issue reliably :(
My environment:
System:
OS: Linux 6.12 Arch Linux
CPU: (16) x64 AMD Ryzen 7 PRO 4750U with Radeon Graphics
Memory: 24.67 GB / 30.58 GB
Container: Yes
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.10.0 - ~/.nvm/versions/node/v20.10.0/bin/node
Yarn: 1.22.22 - /usr/bin/yarn
npm: 10.2.3 - ~/.nvm/versions/node/v20.10.0/bin/npm
pnpm: 10.0.0 - /usr/bin/pnpm
Browsers:
Brave Browser: 132.1.74.48
Chromium: 132.0.6834.83
npmPackages:
react-router: ^7.1.3 => 7.1.3
vite: ^6.0.11 => 6.0.11
Using vitest 3.0.3 and I also have the Storybook addon:
# npm list react-router
@open-formulieren/[email protected] /home/bbt/code/open-forms-sdk
├── [email protected]
└─┬ [email protected]
└── [email protected] deduped
Traceback
Error: Uncaught [Error: useLocation() may be used only in the context of a <Router> component.]
at reportException (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
at innerInvokeEventListeners (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:353:9)
at invokeEventListeners (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3)
at HTMLUnknownElementImpl._dispatch (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9)
at HTMLUnknownElementImpl.dispatchEvent (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17)
at HTMLUnknownElement.dispatchEvent (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34)
at Object.invokeGuardedCallbackDev (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:4213:16)
at invokeGuardedCallback (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:4277:31)
at beginWork$1 (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:27451:7)
at performUnitOfWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26560:12) Error: useLocation() may be used only in the context of a <Router> component.
at invariant (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:327:11)
at useLocation (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:4229:3)
at useSearchParams (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:7606:18)
at App (/home/bbt/code/open-forms-sdk/src/components/App.jsx:11:20)
at renderWithHooks (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:16305:18)
at mountIndeterminateComponent (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:20074:13)
at beginWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:21587:16)
at HTMLUnknownElement.callCallback (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:4164:14)
at HTMLUnknownElement.callTheUserObjectsOperation (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
at innerInvokeEventListeners (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25)
Error handled by React Router default ErrorBoundary: Error: useLocation() may be used only in the context of a <Router> component.
at invariant (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:327:11)
at useLocation (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:4229:3)
at useSearchParams (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:7606:18)
at App (/home/bbt/code/open-forms-sdk/src/components/App.jsx:11:20)
at renderWithHooks (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:16305:18)
at mountIndeterminateComponent (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:20074:13)
at beginWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:21587:16)
at beginWork$1 (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:27426:14)
at performUnitOfWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26560:12)
at workLoopSync (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26466:5)
Error handled by React Router default ErrorBoundary: Error: useLocation() may be used only in the context of a <Router> component.
at invariant (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:327:11)
at useLocation (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:4229:3)
at useSearchParams (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:7606:18)
at App (/home/bbt/code/open-forms-sdk/src/components/App.jsx:11:20)
at renderWithHooks (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:16305:18)
at mountIndeterminateComponent (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:20074:13)
at beginWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:21587:16)
at beginWork$1 (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:27426:14)
at performUnitOfWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26560:12)
at workLoopSync (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26466:5)
Error: Uncaught [Error: useLocation() may be used only in the context of a <Router> component.]
at reportException (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
at innerInvokeEventListeners (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:353:9)
at invokeEventListeners (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3)
at HTMLUnknownElementImpl._dispatch (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9)
at HTMLUnknownElementImpl.dispatchEvent (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17)
at HTMLUnknownElement.dispatchEvent (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34)
at Object.invokeGuardedCallbackDev (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:4213:16)
at invokeGuardedCallback (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:4277:31)
at beginWork$1 (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:27451:7)
at performUnitOfWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26560:12) Error: useLocation() may be used only in the context of a <Router> component.
at invariant (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:327:11)
at useLocation (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:4229:3)
at useSearchParams (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:7606:18)
at App (/home/bbt/code/open-forms-sdk/src/components/App.jsx:11:20)
at renderWithHooks (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:16305:18)
at mountIndeterminateComponent (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:20074:13)
at beginWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:21587:16)
at HTMLUnknownElement.callCallback (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:4164:14)
at HTMLUnknownElement.callTheUserObjectsOperation (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
at innerInvokeEventListeners (/home/bbt/code/open-forms-sdk/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25)
Error handled by React Router default ErrorBoundary: Error: useLocation() may be used only in the context of a <Router> component.
at invariant (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:327:11)
at useLocation (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:4229:3)
at useSearchParams (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:7606:18)
at App (/home/bbt/code/open-forms-sdk/src/components/App.jsx:11:20)
at renderWithHooks (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:16305:18)
at mountIndeterminateComponent (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:20074:13)
at beginWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:21587:16)
at beginWork$1 (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:27426:14)
at performUnitOfWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26560:12)
at workLoopSync (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26466:5)
Error handled by React Router default ErrorBoundary: Error: useLocation() may be used only in the context of a <Router> component.
at invariant (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:327:11)
at useLocation (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:4229:3)
at useSearchParams (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/index.js:7606:18)
at App (/home/bbt/code/open-forms-sdk/src/components/App.jsx:11:20)
at renderWithHooks (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:16305:18)
at mountIndeterminateComponent (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:20074:13)
at beginWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:21587:16)
at beginWork$1 (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:27426:14)
at performUnitOfWork (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26560:12)
at workLoopSync (/home/bbt/code/open-forms-sdk/node_modules/react-dom/cjs/react-dom.development.js:26466:5)
The above error occurred in the <App> component:
at App (/home/bbt/code/open-forms-sdk/src/components/App.jsx:6:49)
at RenderedRoute (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/dom-export.js:3701:26)
at RenderErrorBoundary (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/dom-export.js:3660:5)
at DataRoutes (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/dom-export.js:4114:3)
at Router (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/dom-export.js:4121:13)
at RouterProvider (/home/bbt/code/open-forms-sdk/node_modules/react-router/dist/development/dom-export.js:3944:11)
at RouterProvider2
at IntlProvider (/home/bbt/code/open-forms-sdk/node_modules/react-intl/src/components/provider.js:33:47)
at I18NManager (/home/bbt/code/open-forms-sdk/src/i18n.jsx:46:9)
at I18NErrorBoundary (/home/bbt/code/open-forms-sdk/src/i18n.jsx:80:5)
at NonceProvider (/home/bbt/code/open-forms-sdk/node_modules/react-select/dist/react-select.cjs.dev.js:60:20)
Final edit: I'm probably using this wrong, because if I use RouterProvider not from react-router/dom then I don't have this issue - likely it's more related about running in jest-dom rather than a real browser.
@acelaya I think I've found the root cause - since I was able to use your repo to reproduce the issue and "fix" it by applying a workaround.
Looking at the bundles in node_modules/react-router/dist/development/ you find two separate .js bundles:
dom-export.jsindex.js
Which are referred to from the package.json exports: https://github.com/remix-run/react-router/blob/7a270cbc97bf3c9d73448401eef1d99699bf8bb6/packages/react-router/package.json#L24
These are actual bundles, meaning that the RouterProvider components are defined in each of them, so they're separate JS objects in the runtime and that's the root cause of the problem. Whenever you use RouterProvider from react-router/dom it's supposed to wrap RouterProvider from react-router, but it's actually an entirely different copy. We import the hooks from react-router, so they're using pointing to different copies.
The ESM bundles (dom-export.mjs and index.mjs) don't have this problem, since they both import the shared API from the same chunk, meaning that RouterProvider in both is the same thing, and as such, so is the context it provides and the hook from the main package has access to the context of the react-router/dom wrapper RouterProvider.
You can verify this by editing node_modules/react-router/package.json and changing the references to the .js files to the .mjs files (just changing the extension is enough), and now everything works as expected.
Shell output after this modification:
➜ peer-react-router-vitest git:(main) npm run test
> [email protected] test
> vitest
DEV v3.0.2 /tmp/peer-react-router-vitest
stdout | test/DummyComponent.test.tsx > DummyComponent > fails to import correct router instance
{}
✓ test/DummyComponent.test.tsx (1 test) 16ms
✓ DummyComponent > fails to import correct router instance
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 12:07:11
Duration 984ms (transform 58ms, setup 122ms, collect 222ms, tests 16ms, environment 359ms, prepare 85ms)
PASS Waiting for file changes...
press h to show help, press q to quit
Thanks @sergei-maertens, but yeah, the root cause was already known, and discussed in https://github.com/remix-run/react-router/issues/12475
I reported this to see why with certain versions of node, and when using vitest, the wrong bundle was being resolved for packages that peer-depend on react-router.
I don't know if the fix should be done in react-router or some other package.
Ah damn, I've been reading through some many issues and still dismissed that one...
IMO the bundling/packaging problem is something to fix in react-router because anything not able to use the modules is affected by this and it looks like there's no workaround for it (except for manually providing the flushSync implementation I suppose).
I'm going to dive a bit deeper in the module resolution and see if I can either figure out why it's happening or if there's some Vite config that would make it possible to point it to the right imports.
Thanks for the extra insights!
Thanks @sergei-maertens, but yeah, the root cause was already known, and discussed in #12475
Hrm, I read through the entire issue and I'm not sure if the conclusion is the same - that issue seems to be more about the module-sync not being picked up consistently, but I don't see it address the different identities present in index.js and dom-exports.js for the same source code.
My tests pass if I update my import from import {RouterProvider} from 'react-router/dom'; to import {RouterProvider} from 'react-router';, because (I suspect) they're all relying on react-router/dist/development/index.js, while the correct usage leads to hooks and context objects being used from react-router/dist/development/index.js while the provider from react-router/dist/development/dom-exports.js has different hooks and context objects that lead to the mismatch. This problem is resolved when you use the ESM exports because they happen to import all those objects from the same chunks, thereby giving them the same identity, and I get the feeling that this is more by coincidence than deliberate chunk design.
If the CJS bundles (index.js and dom-exports.js) would also import/require from the same shared module, I suspect this problem wouldn't exist in the first place, so my suspicion is that fully standalone CJS bundles are the root cause.
Okay, I didn't manage to fully understand why vitest seems to prefer the CommonJS build - maybe it's because there's no module conditional in the react-router package.json, but overriding the module resolution via the Vite config works with Vitest and seems to work with my dev and production builds too:
diff --git a/vite.config.mts b/vite.config.mts
index 472f0ed7..7921afa9 100644
--- a/vite.config.mts
+++ b/vite.config.mts
@@ -4,6 +4,7 @@ import {codecovVitePlugin} from '@codecov/vite-plugin';
import replace from '@rollup/plugin-replace';
import {sentryVitePlugin} from '@sentry/vite-plugin';
import react from '@vitejs/plugin-react';
+import path from 'path';
import type {OutputOptions} from 'rollup';
import {defineConfig} from 'vite';
import jsconfigPaths from 'vite-jsconfig-paths';
@@ -164,6 +165,16 @@ export default defineConfig(({mode}) => ({
uploadToken: process.env.CODECOV_TOKEN,
}),
],
+ resolve: {
+ alias: {
+ // ensure react-router imports don't end up with multiple copies/installations. See
+ // https://github.com/remix-run/react-router/issues/12785 for more context.
+ 'react-router/dom': path.resolve(
+ './node_modules/react-router/dist/development/dom-export.mjs'
+ ),
+ 'react-router': path.resolve('./node_modules/react-router/dist/development/index.mjs'),
+ },
+ },
build: {
target: 'modules', // the default
assetsInlineLimit: 8 * 1024, // 8 KiB
Okay, I didn't manage to fully understand why vitest seems to prefer the CommonJS build - maybe it's because there's no
moduleconditional in the react-router package.json, but overriding the module resolution via the Vite config works with Vitest and seems to work with my dev and production builds too:index 472f0ed7..7921afa9 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -4,6 +4,7 @@ import {codecovVitePlugin} from '@codecov/vite-plugin'; import replace from '@rollup/plugin-replace'; import {sentryVitePlugin} from '@sentry/vite-plugin'; import react from '@vitejs/plugin-react'; +import path from 'path'; import type {OutputOptions} from 'rollup'; import {defineConfig} from 'vite'; import jsconfigPaths from 'vite-jsconfig-paths'; @@ -164,6 +165,16 @@ export default defineConfig(({mode}) => ({ uploadToken: process.env.CODECOV_TOKEN, }), ], + resolve: { + alias: { + // ensure react-router imports don't end up with multiple copies/installations. See + // https://github.com/remix-run/react-router/issues/12785 for more context. + 'react-router/dom': path.resolve( + './node_modules/react-router/dist/development/dom-export.mjs' + ), + 'react-router': path.resolve('./node_modules/react-router/dist/development/index.mjs'), + }, + }, build: { target: 'modules', // the default assetsInlineLimit: 8 * 1024, // 8 KiB
This is a nice workaround for node >=22.12, but it seems to have the opposite effect, and makes it fail with older node versions.
I guess it would be possible to detect the node version and add that dynamically.
That's... Interesting since I'm on Node 20 😬
I found a not so terrible workaround, inspired on @sergei-maertens proposal.
Since I'm only experiencing this problem when running tests with vitests, I used the aliasing resolution only inside the test block, to not affect vite dev server or vite bundling.
If you use different config files for vite and vitest, then you can use it as described there.
Additionally, adding the alises fixes the problem for node >22.10, but it breaks it for older versions, where it works without the aliases. Hence, I have added a small logic to check current node version, and dynamically add the aliases only if running in node >22.10.
vite.config.ts
import { defineConfig } from 'vitest/config';
+ const DEFAULT_NODE_VERSION = 'v22.10.0';
+ const nodeVersion = process.version ?? DEFAULT_NODE_VERSION;
export default defineConfig({
plugins: [/* [...] */],
// [...]
test: {
// [...]
+ alias: nodeVersion > DEFAULT_NODE_VERSION
+ ? {
+ 'react-router': resolve(__dirname, 'node_modules/react-router/dist/development/index.mjs'),
+ }
+ : undefined,
}
});
Notice the way the node version is compared (nodeVersion > DEFAULT_NODE_VERSION) is a bit brittle. A proper version comparison would be better, but this gives you an idea.
If you're experiencing this issue, can you try adding the following to the Vite config you use for Vitest and report back?
resolve: {
conditions: ["module-sync"],
},
If you're experiencing this issue, can you try adding the following to the Vite config you use for Vitest and report back?
resolve: { conditions: ["module-sync"], },
Yep, it does fix it for node >=22.12, but it makes it fail with older versions.
Off on a tangent, for tormented miserable souls out there who are still stuck in CRA project (like me), here's the CRA counterpart (webpack config) using react-app-rewired's override and it works for me :v
module.exports = function override(config) {
const reactRouterPath = path.resolve(
__dirname,
'node_modules/react-router/dist/development/index.mjs',
);
const moduleScopePlugin = config.resolve.plugins.find(
(plugin) => plugin.constructor.name === 'ModuleScopePlugin',
);
moduleScopePlugin.allowedFiles.add(reactRouterPath);
moduleScopePlugin.allowedPaths.push(path.dirname(reactRouterPath));
config.resolve.alias = {
...config.resolve.alias,
'react-router': reactRouterPath,
};
return config;
};
@markdalgleish I was having this issue in Vitest and adding the resolve conditions module sync block to vitest.config.ts solved it!
Hey folks - we are fixing the bundling issue mentioned above in https://github.com/remix-run/react-router/pull/13497 and that's available in an experimental release (0.0.0-experimental-818f8e08d) if anyone wants to see if that helps resolve their issues. I think that will resolve any issues on versions of node <22.12 but I believe there is still an issue on node 22.12 and above.
@markdalgleish Bringing the conversation from #12512 over here since I think it's the same issue.
In that reproduction it's not a 3rd party lib with a peerDep that imports react-router, but instead react-router/dom which imports from react-router. It also only starts happening on node 22.12 there as well.
I did some instrumentation on my branch in the repro and was able to confirm:
- our
importstatements from the test file both land in the CJS version ofreact-routerandreact-router/dom- this creates the first set of RR React Contexts
- but then when the CJS version of
react-router/domdoesrequire('react-router'), that loads the ESM version of react -router- this creates the second set of React contexts
The source code looks like this:
import { createMemoryRouter, RouterProvider, useNavigate } from "react-router";
import { RouterProvider as RouterProviderDOM } from "react-router/dom";
And my instrumented logs come out as:
> npm run test
...
Loaded index.js (CJS), creating contexts # From import {} from 'react-router'
Loaded dom-export.js (CJS) # From import {} from 'react-router/dom'
calling require("react-router") from dom-export.js
Loaded chunk-XXXXXXX.js (ESM), creating contexts (ESM) # require() loads the ESM version 🤔
done require("react-router") from dom-export.js
I can also confirm that adding resolve: { conditions: ["module-sync"] } to the vite config in my repro fixes the issue.
How did you do this instrumentation @brophdawg11, pretty sure I tried the above and still had the issue when running the app in dev mode.
I just added console logs to the files in node_modules/react-router/dist/develoment to see which files loaded at runtime
@brophdawg11 https://github.com/remix-run/react-router/pull/13497 doesn't seem to work for me. I still need the fix proposed earlier in this thread (which solved the problem for me).
'react-router': resolve(__dirname, 'node_modules/react-router/dist/development/index.mjs'),
@EmiBemi They are 2 separate issues - this node 22.12 issue is still open/unresolved
Hey thanks everyone. Also had this exact issue and it's been nagging me since Node 22.12 came out and I was able to fix it after some of the hints suggested in this thread.
We somehow had both react-router-dom and react-router existing in package-lock.json even though we just only had react-router-dom defined in package.json (we weren't yet across the new suggested namespace as mentioned here https://www.npmjs.com/package/react-router-dom), but now we only just have the single react-router dependency. Check if your node_modules has both react-router-dom and react-router, in which both belong to remix-run. You are probably impacted too.
This one for me was caused by (I think) having the libraries in my mono repo set to moduleResolution: 'NodeNext', making everything Bundler improved the resolution, the transitive dependencies in the libraries would sometimes get resolves as commonjs rather than ESM.
I also wonder if open telemetry and require in the middle may also be causing some havoc.
It's hard for me to isolate where I fixed the issue, because we had a bunch of libraries which did major version bumps for the Remix -> RR7 change, but also made other breaking changes when they made that change. Made the switch way bigger than I'd like.