dotnet/aspnetcore · critical
Only one interactive runtime may enable navigation intercept
Error message
Only one interactive runtime may enable navigation interception at a time.
What it means
Thrown by setHasInteractiveRouter() in NavigationUtils.ts:154 when a second interactive Blazor renderer tries to register as the navigation-intercepting router. Blazor's client-side navigation interception (click handling on anchors, popstate, etc.) can only be owned by a single interactive runtime, because two routers would race to interpret and handle the same navigation events. The guard compares the already-registered rendererId against the new one and throws if they differ.
Source
Thrown at src/Components/Web.JS/src/Services/NavigationUtils.ts:154
if (candidate instanceof HTMLAnchorElement || candidate instanceof SVGAElement) {
return candidate;
}
}
}
return null;
}
export function hasInteractiveRouter(): boolean {
return interactiveRouterRendererId !== undefined;
}
export function getInteractiveRouterRendererId() : WebRendererId | undefined {
return interactiveRouterRendererId;
}
export function setHasInteractiveRouter(rendererId: WebRendererId) {
if (interactiveRouterRendererId !== undefined && interactiveRouterRendererId !== rendererId) {
throw new Error('Only one interactive runtime may enable navigation interception at a time.');
}
interactiveRouterRendererId = rendererId;
}
View on GitHub (pinned to 294cab2f9b)
Solutions
- Ensure only one interactive runtime owns routing — for InteractiveAuto, the WebAssembly runtime should replace (not add to) the Server runtime; verify you are not double-registering both in production.
- If you have a legitimate need for multiple runtimes, design only one of them to handle navigation and keep the others from calling setHasInteractiveRouter.
- Check for accidental double script inclusion of blazor.web.js / blazor.webassembly.js in the host page.
- On teardown or before re-init, call the equivalent reset path so interactiveRouterRendererId returns to undefined before a different renderer registers.
Example fix
// before — both runtimes call this
setHasInteractiveRouter(WebRendererId.Server);
setHasInteractiveRouter(WebRendererId.WebAssembly); // throws
// after — only the active router registers; the wasm runtime takes over after server relinquishes
if (!hasInteractiveRouter()) {
setHasInteractiveRouter(currentRendererId);
} Defensive patterns
Strategy: validation
Validate before calling
import { hasInteractiveRouter } from './NavigationUtils';
function safeSetInteractiveRouter(id: WebRendererId) {
if (hasInteractiveRouter()) {
console.warn('Navigation interception already enabled; skipping re-registration.');
return;
}
setHasInteractiveRouter(id);
} Type guard
function canRegisterRouter(rendererId: WebRendererId): boolean {
return !hasInteractiveRouter();
} Prevention
- Designate exactly one runtime as the navigation owner before booting any interactive runtime.
- Guard setHasInteractiveRouter with hasInteractiveRouter() to avoid double registration.
- Avoid loading both blazor.server.js and blazor.webassembly.js routers on a single page.
- Reset the registered router on full teardown before re-initializing in development.
When it happens
Trigger: Calling setHasInteractiveRouter(rendererId) a second time with a different WebRendererId while a previous rendererId is already registered (interactiveRouterRendererId !== undefined && !=== rendererId). Happens when both InteractiveServer and InteractiveWebAssembly rendermodes initialize their routers in the same document.
Common situations: Blazor Web App with InteractiveAuto rendermode transitioning from Server to WebAssembly where both runtimes start and each calls setHasInteractiveRouter. Manually bootstrapping multiple Blazor runtimes on one page. Hot-reload or re-initialization during development that re-runs startup without the first runtime releasing control. Misconfigured custom hosting that loads two blazor.*.js runtimes.
Related errors
- Dynamic root components have already been enabled.
- Root components cannot be marked as interactive. The <html>
- Enhanced navigation does not support making a non-GET reques
- Enhanced navigation does not support making a non-GET reques
- Cannot perform enhanced form submission that changes the URL
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/86efb65d6eba57a2.
Report an issue: GitHub.