Skip to content

Start: HMR does not refresh loaders or beforeLoad #5698

@daveycodez

Description

@daveycodez

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

  1. Create a new project:
   pnpm create @tanstack/start@latest
  1. Add this to src/routes/index.tsx:
export const Route = createFileRoute("/")({
  component: App,
  beforeLoad: async () => {
    console.log("hi");
  },
});
  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions