ENOENT error whenever I change a route module's exports
I'm using React Router as a...
framework, SSR-enabled
Reproduction
Unfortunately I have not been able to create a minimal reproduction. The only place I see this is in our (Docker) internal web monorepro. I'd be more than happy to get on a Zoom call or similar with somebody to demonstrate the issue.
System Info
System:
OS: macOS 15.4.1
CPU: (12) arm64 Apple M2 Max
Memory: 4.35 GB / 64.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.15.0 - ~/.local/state/fnm_multishells/61607_1747190035669/bin/node
npm: 11.3.0 - ~/.local/state/fnm_multishells/61607_1747190035669/bin/npm
pnpm: 10.7.1 - ~/.local/state/fnm_multishells/61607_1747190035669/bin/pnpm
bun: 1.2.5 - ~/.bun/bin/bun
Browsers:
Brave Browser: 111.1.49.132
Chrome: 136.0.7103.93
Edge: 136.0.3240.64
Safari: 18.4
Used Package Manager
yarn
Expected Behavior
I should be able to change a route module's exports (default, loader, action, clientAction, `clientLoader, etc…) and HMR should work and I should be able to reload the page in my browser without error.
Actual Behavior
When I add/remove one of the React Router route module exports, my dev server reloads, but I see this error in terminal output, and my express error handler is always triggered (this is the error handler that is added after the React Router handler, and is used as a "catch-all" final error handler when things go catastrophically wrong).
11:13:16 AM [vite] Pre-transform error: ENOENT: no such file or directory, open './build/client/.vite/manifest.json'
11:13:16 AM [vite] Error when evaluating SSR module virtual:react-router/server-build:
|- Error: ENOENT: no such file or directory, open './build/client/.vite/manifest.json'
This error gets "stuck" and I have to kill the process with SIGINT (Ctrl-C) and restart the process to get things working again.
What does your vite.config.ts or react-router.config.ts (if you have one) look like?
Based on what you've said, you've got a custom Express server instead of using the @react-router/dev server. Does your setup look significantly different from this?
const viteDevServer =
process.env.NODE_ENV === 'production'
? undefined
: await import('vite').then(vite =>
vite.createServer({
server: { middlewareMode: true, hmr: { server }, allowedHosts: true },
appType: 'custom'
})
)
const reactRouterHandler = createRequestHandler({
build: viteDevServer
? () => viteDevServer.ssrLoadModule('virtual:react-router/server-build')
: await import('./build/server/index.js')
})
Yeah that's basically what we have, although we do some things to wrap the loaded route modules (instrumentation and such) before handing the ServerBuild off to createRequestHandler()
I can try removing those enhancers and seeing if I can still reproduce the issue.
react-router.config.ts is effectively this:
export default {
serverModuleFormat: 'esm',
future: {
unstable_middleware: true,
unstable_optimizeDeps: true,
},
ssr: true,
};
vite.config.ts is a bit complicated, but the plugins part looks like this:
plugins: [
envOnlyMacros(),
viteCommonjs(),
mdx({ remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter] }),
tsconfigPaths({ loose: true }),
...(process.env['NODE_ENV'] !== 'test'
? [reactRouter(), safeRoutes({ outDir: 'types' })]
: []),
cjsInterop({
dependencies: [
'@mui/system/**',
'attr-accept',
'uuid',
// Prod build only
...(process.env['NODE_ENV'] === 'production'
? ['safe-stringify', 'indent-string']
: []),
],
}),
],
I might be able to trim that down, but we're still on MUI 6, which has rather severely broken packaging, which is why we need the viteCommonjs() and cjsInterop() plugins.
We've had to apply the workaround that @markdalgleish wrote about here as well.
Note: I've had conversations on Discord with @brophdawg11 and @pcattori about this, it was requested that I make a GH issue so that it doesn't slip through the cracks.
To align with our new Open Governance model, we are now requiring that all issues have a minimal and runnable reproduction. To that end, we're doing some housekeeping in the repo to clean up existing issues that do not have a valid reproduction. This should get us down to a more manageable number of issues and allow us to be more responsive to existing and newly filed issues.
We're using a Github actions script to identify issues without a reproduction by looking for a StackBlitz, CodeSandbox, or Github link in the issue body. This won't be perfect, so if this issue has a reproduction on another platform, please comment back on here and we can re-open the issue. Similarly, if there's a reproduction buried in a comment, please move the link into the description and comment back. Please tag @brophdawg11 or @brookslybrand in your comment so we get a notification as well 🙂.
If this issue did not have a reproduction but is still valid, or if you wish to start with a fresh issue, please create a new issue with a fresh reproduction against v7 and link to this issue in the new description.
If this a feature request, please open a new Proposal Discussion in React Router, and if it gets enough community support it can be considered for implementation.
If you have any questions you can always reach out on Discord. Thanks again for providing feedback and helping us make React Router even better!
Reopening based on @rossipedia's last comment
Hey there, I'm experiencing the same issue since we made the move from Remix to RR7. Our app also uses an Express server, and its config follows closely the Epic Stack's one
On my side, I see this error each time I add/remove a new file, and more generally, when doing filesystem changes.
I dug a bit more to try to identify a potential cause and found out that it comes from this part -> https://github.com/remix-run/react-router/blob/422186644b4af7e0fae2934871ec9b0cddb5c757/packages/react-router-dev/vite/plugin.ts#L2038
When serving my app for the first time, viteCommand = "serve", but when I do a fs change, it triggers a vite [react-router] Config changed. and now viteCommand = "build".
I'll continue digging, but I hope this info will be helpful to anyone.
EDIT:
I think I found the culprit, and it comes from the safe-routes vite plugin, which you're also using @rossipedia.
When the vite config changes and gets reloaded, this line is called:
https://github.com/yesmeck/safe-routes/blob/main/src/vite.ts#L45
It changes the viteConfigEnv.command to "build" while we're in dev mode and then triggers the issue.
When I remove this plugin, I don't have the error anymore. I'll open an issue on the safe-routes repo about this.