#14101·react-router

Safari HMR Broken After Page Refresh in React Router 7.1.0+

Author: 0xcybertimCreated Jul 30, 2025Updated Aug 27, 2026
Labelsbugpkg:@react-router/devfeat:vite

Reproduction

  1. Create a new React Router 7.6.0 project with Vite
  2. Start development server (npm run dev)
  3. Open the app in Safari desktop
  4. Make changes to a route file (e.g., app/routes/home.tsx)
  5. Observe HMR working correctly
  6. Refresh the page in Safari
  7. Notice the page loads the old cached version with hydration errors

Environment: Any React Router 7.1.0+ project with Vite dev server

System Info

System:
    OS: macOS (darwin 24.2.0)
  Binaries:
    Node: 22.14.0
    npm: 10.7.0
  Browsers:
    Safari: Affected
    Chrome: Works fine
    Firefox: Works fine
  npmPackages:
    react-router: 7.6.0
    @react-router/dev: 7.6.0
    @react-router/node: 7.6.0
    vite: 6.0.3

Used Package Manager

npm

Expected Behavior

After making changes to route files and refreshing the page in Safari:

  • Page should load the latest version of the code
  • No hydration errors should occur
  • HMR should continue working normally

Actual Behavior

After making changes to route files and refreshing the page in Safari:

  • Page loads an old cached version of the route
  • Hydration errors occur due to version mismatch
  • Development workflow is broken for Safari users

Issue Flow:

  1. Load app → ✅ Works
  2. Make changes → ✅ HMR works
  3. Make more changes → ✅ HMR continues working
  4. Refresh page → ❌ BROKEN - Loads old cached version with hydration errors

Browser Scope: Safari desktop only (Chrome/Firefox unaffected) Affected Versions: 7.1.0 through 7.6.0+

Root Cause & Fix

React Router 7.1.0+ removed query string concatenations from route imports, breaking Safari's module cache invalidation. Safari requires query parameters to distinguish between different versions of route files.

Fix: Restore ?__react-router-build-client-route query strings in packages/react-router-dev/vite.ts at lines ~2920, ~4022, and ~3950.

Required Fixes

File: packages/react-router-dev/vite.ts (or compiled dist/vite.js)

Fix 1 - Restore routeModulePath query string (~line 2920):

let routeModulePath = combineURLs(
  ctx.publicPath,
-  `${resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig))}`
+  `${resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig))}?__react-router-build-client-route`
);

Fix 2 - Restore moduleUrl query string (~line 4022):

let moduleUrl = combineURLs(
  ctx.publicPath,
-  `${resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig))}`
+  `${resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig))}?__react-router-build-client-route`
);

Fix 3 - Fix Fast Refresh compatibility (~line 3950):

function addRefreshWrapper(reactRouterConfig, code, id) {
-  let route = getRoute(reactRouterConfig, id);
+  // Strip query string for route lookup (e.g., remove ?__react-router-build-client-route)
+  let idForRouteMatch = id.split('?')[0];
+  let route = getRoute(reactRouterConfig, idForRouteMatch);

Validation

Tested Fix: Applied manually to node_modules/@react-router/dev/dist/vite.js in React Router 7.6.0
Result: Safari HMR now works correctly after page refresh
No Regression: Chrome/Firefox continue working as expected