-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Which project does this relate to?
Start
Describe the bug
Hot Module Reloading (HMR) does not reflect changes made to loader functions when their code is modified during development. Even though Vite detects the file change and logs indicate a hot update, the return value from the loader remains cached and unchanged until a full page refresh is performed.
The loader function is executed (as evidenced by console.log statements updating), but the data returned by the loader is not updated in the component that consumes it via Route.useLoaderData().
This behavior makes it extremely difficult to develop features that rely on loader data, as developers must perform full page refreshes after every change to see the updated data, defeating the purpose of HMR.
Your Example Website or App
https://github.com/ericksonholguin/Tanstack-Start-hot-reload-issues
Steps to Reproduce the Bug or Issue
- Create a new TanStack Start project:
pnpm create @tanstack/start@latest- Add a simple loader to
src/routes/index.tsx:
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: RouteComponent,
loader: () => {
console.log('Loader called')
return {
message: 'Original message',
}
},
})
function RouteComponent() {
const data = Route.useLoaderData()
return <div>{data.message}</div>
}- Run the dev server:
pnpm dev-
Open the application in your browser and observe the message "Original message" being displayed.
-
Edit the loader to return a different message:
loader: () => {
console.log('Loader called - UPDATED')
return {
message: 'UPDATED message - this should show immediately',
}
},- Save the file and observe the behavior.
Observed behavior:
- Vite logs show:
[vite] (client) hmr update /src/routes/index.tsx - The console.log shows the updated message ("Loader called - UPDATED")
- The UI still displays "Original message" (the old value)
- Only after a full page refresh does the UI update to show "UPDATED message - this should show immediately"
Additional observations:
- The loader function is being re-executed (proven by updated console.log output)
- The return value is being cached somewhere and not invalidated during HMR
- Using the same logic in
useEffector component state works correctly with HMR - Setting
defaultPreloadStaleTime: 0in the router configuration does not resolve the issue - Adding
defaultGcTime: 0anddefaultStaleTime: 0in development mode does not resolve the issue
Expected behavior
When a loader function is modified during development, the HMR system should:
- Re-execute the loader function (✅ this works)
- Invalidate the cached loader data
- Update the component with the new loader data immediately
- Reflect the changes in the browser without requiring a full page refresh
This is the expected behavior for any development workflow with HMR, and matches how component code, styles, and other React code behaves with Vite's HMR.
Screenshots or Videos
Screen.Recording.2026-01-31.at.7.30.34.AM.mp4
Platform
- Router / Start Version: 1.132.0
- OS: macOS 26.2
- Browser: Chrome
- Browser Version: Version 144.0.7559.110 (Official Build) (arm64)
- Bundler: Vite
- Bundler Version: 7.1.7
- Runtime: Bun v1.3.0
- React Version: 19.2.0
Additional context
ext
Related Issues
This issue appears to be related to or a continuation of:
- Issue Start: HMR does not refresh loaders or beforeLoad #5698: "Start: HMR does not refresh loaders or beforeLoad" (which was supposedly fixed in PR fix: route HMR handling #5710)
- However, the issue persists in version 1.132.0
What we've tried
We attempted multiple solutions without success:
-
Router configuration changes:
- Added
defaultPreloadStaleTime: 0 - Added
defaultGcTime: 0anddefaultStaleTime: 0in dev mode - None of these affected the caching behavior
- Added
-
Custom Vite plugin to invalidate modules:
function tanstackRouterHMR(): Plugin { return { name: 'tanstack-router-hmr', enforce: 'post', handleHotUpdate(ctx) { if (ctx.file.includes('/routes/') && ctx.file.endsWith('.tsx')) { // Invalidate router and routeTree modules // This caused full page reloads instead of HMR updates } }, } }
- This approach caused full page reloads, not true HMR
-
Per-route HMR invalidation:
if (import.meta.hot) { import.meta.hot.accept(() => { import.meta.hot?.invalidate() }) }
- This caused infinite reload loops
Impact
This bug severely impacts developer experience when working with:
- Dynamic data fetching in loaders
- Localization/i18n data loaded via loaders
- API response mocking during development
- Any feature that depends on loader data
Developers are forced to perform full page refreshes constantly, which:
- Slows down development significantly
- Loses component state on every refresh
- Makes iterative development frustrating
Suspected root cause
Based on our investigation, it appears that:
- The loader function itself is properly re-executed with HMR
- However, the loader's return value is cached at a layer that's not invalidated during HMR
- This cache might be in the SSR/hydration layer or in TanStack Router's internal cache
- The cache is only cleared on full page reload, not on HMR updates
We would appreciate any guidance on:
- Whether this is a known limitation
- If there's a workaround we haven't tried
- If this is being actively worked on
- What the proper fix would involve
Thank you for maintaining this excellent framework!