-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Which project does this relate to?
Start
Describe the bug
Hot Module Reloading (HMR) does not re-run beforeLoad or loader functions when their code changes.
Even though Vite logs indicate a hot update ([vite] program reload), the functions retain old behavior until a full page refresh.
This causes issues when working on things like localization or dynamic imports that rely on beforeLoad.
Your Example Website or App
create-tanstack base project
Steps to Reproduce the Bug or Issue
- Create a new project:
pnpm create @tanstack/start@latest- Add this to src/routes/index.tsx:
export const Route = createFileRoute("/")({
component: App,
beforeLoad: async () => {
console.log("hi");
},
});- Run the dev server and edit the console.log message.
Observed behavior:
• The console still prints the old message until a full refresh.
• The Vite terminal logs show:
[vite] (client) hmr update /src/routes/index.tsx
[vite] (ssr) page reload src/routes/index.tsx
[vite] program reload
• Using the same code in a useEffect does hot reload properly.
Adding this snippet at the bottom of each route fixes the problem:
if (import.meta.hot) {
import.meta.hot.accept(() => {
import.meta.hot.invalidate();
});
}…but it’s not practical to add to every route file manually.
Expected behavior
HMR should automatically re-run updated beforeLoad and loader code without requiring a full page refresh.
Additional context
💡 Suggested Fix
A simple Vite plugin resolves the issue by invalidating router modules when route files change:
import type { Plugin } from "vite"
export function tanstackRouterHMR(): Plugin {
return {
name: "tanstack-router-hmr",
enforce: "post",
handleHotUpdate(ctx) {
const invalidatedModules = []
for (const mod of ctx.server.moduleGraph.idToModuleMap.values()) {
if (mod.id?.includes("/router.ts")) {
invalidatedModules.push(mod)
}
}
return invalidatedModules
},
}
}This ensures route changes propagate correctly through routeTree.gen.ts and router.tsx.
⸻
🧩 Context
Discovered while editing .json message files for i18next — changes to localized messages didn’t reflect until a hard refresh, even though they were dynamically imported inside beforeLoad.