Skip to content

[Vite plugin][Module Federation] Multiple routers in the same window cause an Invalid Hook Call when the remote uses @tanstack/router-plugin — regression in 1.168.17 #7921

Description

@LFFATE

Which project does this relate to?

Router

Describe the bug

Summary

I am seeing an Invalid hook call error in a Vite Module Federation setup where:

  • the host application creates its own TanStack Router instance;
  • a federated remote creates a completely separate TanStack Router instance;
  • the remote uses the TanStack Router Vite plugin;
  • the host and remote intentionally use isolated React runtimes and isolated React roots.

The problem only occurs when both the host and the remote create a router.

The host does not even need to render its router or mount a RouterProvider. Merely executing:

const router = createRouter({
  routeTree,
})

in the host is enough to make the remote fail.

If I remove that createRouter() call from the host, the remote loads and renders successfully.

The issue is also version-specific:

@tanstack/router-plugin@1.168.16 works;
@tanstack/router-plugin@1.168.17 fails;
newer versions tested after 1.168.17 also fail in the same way.

This strongly suggests a regression introduced in 1.168.17.

The host and remote are separate Vite applications running on separate ports.

The remote is loaded through Module Federation runtime APIs such as:

registerRemotes(...)
loadRemote(...)

The issue is reproducible when:

host: Vite preview / production bundle
remote: Vite dev server

I can provide a minimal reproduction repository if needed.

Architecture

The host and remote are intentionally isolated.

The host has its own React runtime and root:

Host React
└── Host ReactDOM
└── Host root
└── Host Router

The remote has a different React runtime and creates its own independent root:

Remote React
└── Remote ReactDOM
└── Remote root
└── Remote Router

The boundary between the applications is imperative.

The host only passes a DOM element and plain data to the remote:

No React elements, React contexts, providers, router instances, or query clients are passed between the host and the remote.

Module Federation sharing is totally disabled.

Therefore, the remote is expected to be a fully independent React application mounted inside a DOM element owned by the host.

Actual behavior

The host successfully:

  • registers the remote;
  • loads the exposed module;
  • receives the exported init function;
  • calls init;
  • enters the remote code;
  • creates the remote React root.

The failure happens when the remote starts rendering:

root.render()

The error is:

Invalid hook call. Hooks can only be called inside of the body of a function component.

Because init() is called by the host, the exception propagates through the host call stack. Initially, this made it look as though the host itself was throwing the error.

Running the host in preview mode and the remote in dev mode made the error easier to localize because the host-side stack was minified while the remote-side modules remained readable.

The exposed module itself is loaded correctly, and init is a valid function. The failure occurs during the first React render initiated by init.

Why this does not look like an ordinary duplicate React problem

Normally, an Invalid hook call in Module Federation suggests that a component and its renderer are using different React instances.

However, in this case:

  • the host and remote are intentionally isolated;
  • the remote creates its own root with its own react-dom/client;
  • the remote works correctly when the host does not call createRouter;
  • adding a host router instance, without rendering it, causes the remote render to fail;
  • disabling the TanStack Router Vite plugin fixes the issue;
  • downgrading only @tanstack/router-plugin from 1.168.17 to 1.168.16 fixes the issue.

If this were simply an incorrectly shared React dependency, the existence of an unused host router instance should not determine whether the remote’s isolated React tree can render.

The behavior suggests that the router plugin introduces some state or generated code that crosses the application boundary.

Current hypothesis

My current hypothesis is that the Vite plugin’s development/HMR integration assumes that there is only one TanStack Router instance per browser window.

In a Module Federation architecture, the host and multiple remotes can legitimately create independent routers in the same window.

If generated route modules or HMR code use a single global router reference, for example something conceptually similar to:

window.__TSR_ROUTER__

then route modules belonging to the remote may accidentally find or update the router created by the host.

The fact that the host only needs to call createRouter(), without rendering a provider, strongly suggests that the conflict happens at router registration, route-module initialization, or HMR bookkeeping time rather than through React context.

A possible sequence would be:

  1. The host creates its router.
  2. The router or plugin registers host router state globally.
  3. The runtime federation system loads the remote’s development modules.
  4. The remote route modules execute plugin-injected HMR or reconciliation code.
  5. That code sees the host router instead of the remote router.
  6. Route records with common IDs such as the root route are reconciled against
    the wrong router.
  7. Objects from the host and remote dependency graphs become mixed.
  8. The remote starts rendering.
  9. React encounters router/component state associated with another React graph
    and reports an Invalid Hook Call.

This is currently a hypothesis, but it explains all observed conditions:

  • why two createRouter() calls are required;
  • why no host RouterProvider is required;
  • why disabling the Vite router plugin fixes the issue;
  • why shared: {} does not fix it;
  • why 1.168.16 works and 1.168.17 fails.

Complete minimal reproducer

https://github.com/LFFATE/tanstack-repro

Steps to Reproduce the Bug

Minimal host-side condition

The following host code is enough to trigger the problem:

import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

const router = createRouter({
  routeTree,
})

// RouterProvider is not required.
// The router does not need to be rendered.

const remote = await loadRemote('remote/App')

remote.init({
  container: document.getElementById('remote-root')!,
})

If I remove only this code:

const router = createRouter({
  routeTree,
})

the remote renders successfully.

This means the issue is not caused by two active RouterProvider components or by two routers trying to control the same DOM subtree.

A router instance merely existing in the host is sufficient.

Minimal remote-side condition

The remote creates its own router:

import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
})

It then renders it inside the remote’s own React root:

import { RouterProvider } from '@tanstack/react-router'
import { createRoot } from 'react-dom/client'

import { router } from './router'

export function init({
  container,
}: {
  container: HTMLElement
}) {
  const root = createRoot(container)

  root.render(
    <RouterProvider router={router} />,
  )

  return {
    destroy() {
      root.unmount()
    },
  }
}

The remote Vite configuration contains the TanStack Router plugin:

import { tanstackRouter } from '@tanstack/router-plugin/vite'
import react from '@vitejs/plugin-react';
import { federation } from '@module-federation/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      target: 'react',
    }),

    react(),

    federation({
      name: 'remote',

      exposes: {
        './App': './src/AppInit.tsx',
      },

      shared: {},
    }),
  ],
})

When the TanStack Router plugin is removed or route generation through the plugin is disabled, the problem disappears.

For example, this workaround avoids the failure:

tanstackRouter({
  target: 'react',
  enableRouteGeneration: false,
})

Generating the route tree outside the Vite plugin also avoids the issue.

The most important observations are:

  • The remote works when it is the only application creating a TanStack router.
  • The host router does not need to be mounted.
  • The error requires the TanStack Router Vite plugin to be active in the remote.
  • The regression starts specifically at @tanstack/router-plugin@1.168.17.
  • The problem is specific to runtime loading in development.
  • Disabling React sharing does not solve it.
  • Preview/Prod mode for both host and remote fixes the error.

Expected behavior

Multiple independent TanStack Router applications should be able to coexist in the same browser window.

This should work even when:

  • each application has its own React runtime;
  • each application has its own React root;
  • each application has its own generated route tree;
  • each application creates its own router;
  • one application is a Module Federation host;
  • another application is loaded dynamically as a remote;
  • route IDs overlap between applications;
  • both applications use Vite development mode.

The router plugin’s HMR and generated development code should only interact with the router that belongs to the same application/module graph.

A router created by the host should not be visible to or mutated by route modules belonging to a remote.

Screenshots or Videos

No response

Platform

Environment

The relevant setup is approximately:

React: 19.x
Vite: 7.3.1 (8 also tested)
Module Federation: @module-federation/vite 1.20.0
TanStack React Router: 1.170.18
@tanstack/router-plugin:
1.168.16 — works
1.168.17 — fails

Additional context

Read steps to reproduce the issue at repro repo in README.md

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