Skip to content

fix(frontend): re-root federated Console router to avoid host __root collision (MF v2 dev)#2508

Merged
malinskibeniamin merged 3 commits into
masterfrom
jc/fix-console-mf-v2-federated-root
Jun 19, 2026
Merged

fix(frontend): re-root federated Console router to avoid host __root collision (MF v2 dev)#2508
malinskibeniamin merged 3 commits into
masterfrom
jc/fix-console-mf-v2-federated-root

Conversation

@c-julin

@c-julin c-julin commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

When Console is embedded in Cloud UI via Module Federation v2 (enable-console-mf-v2), the embedded app is broken in dev: empty sidebar, a KBar crash (getState is not a function), and a nuqs NUQS-404 ("nuqs requires an adapter") on pages like Topics/Security. All three are one root cause.

Root cause

Console's federated entry (console-app.tsx) builds its router from the generated routeTree, whose root comes from src/routes/__root.tsx. Cloud UI (the host) also has a src/routes/__root.tsx since its TanStack Router migration — an identical rspack module id (./src/routes/__root.tsx) in both apps. In the shared rsbuild/MF dev runtime, the host's already-registered module wins that collision, so Console's route tree ends up with Cloud UI's root route (its RootComponent) but Console's child routes.

Cloud UI's RootComponent renders <CommandPalette/> (KBar) and a nuqs/adapters/react <NuqsAdapter>. Inside Console's subtree there's no KBarProvideruseKBar throws → Console's render aborts → sidebar never populates. And the only nuqs provider present is Cloud UI's react adapter on Cloud UI's nuqs context, while Console's useQueryState consumers read Console's own nuqs instance → no matching provider → NUQS-404.

Why this is surfacing now

It needed a conjunction of recent/rare conditions that only just lined up:

  1. Cloud UI migrated react-router-dom → TanStack Router (2026-01-26) — that's when it first got a src/routes/__root.tsx with the same module id as Console's. Before that, nothing to collide with.
  2. MF v2 embedding is off by default (enable-console-mf-v2 = false). Only the v2 path has Console build its own router from routeTree.gen (which imports __root). The default v1 legacy injector mounts differently and doesn't hit this.
  3. It only manifests in dev. Production builds host and remote as fully isolated runtimes with no shared dev module-id registry, so Console always renders its own __root in prod. (It also only became observable in localdev once Console hot-reload actually started loading the dev bundle — previously localdev was serving the deployed prod Console image.)

So this is a latent dev-only MF hazard, newly exposed — not a regression in normal usage, and not present in production.

Fix

Two files, embedded MF-v2 path only:

  • console-app.tsxcreateFederatedRouteTree() re-parents the generated child routes onto Console's own federatedRootRoute (from federated-routes.tsx, a Console-unique module path that can't collide with the host), and createRouter uses that tree. This guarantees the embedded app renders Console's own root regardless of the dev module-graph crossover.
  • federated-routes.tsxFederatedRootLayout gains RouterSync + RequireAuth. RequireAuth is what triggers api.refreshUserData() → the endpoint-compatibility fetch → the embedded sidebar items (without it the sidebar stays empty). Mirrors __root.tsx's EmbeddedLayout.

Standalone (app.tsx / __root.tsx) and the legacy embedded entry keep using the generated routeTree unchanged.

Verification (localdev, embedded in Cloud UI, dev build)

  • Topics & Security pages: sidebar fully populated (Topics, Schema Registry, Consumer Groups, Security, Quotas, Connect, Transforms, Shadow Links, SQL, AI Agents, …), tables render, no NUQS-404, no KBar crash, no error boundary. Verified across two clusters.
  • Standalone Console (direct) unaffected.

For the reviewer

  • The fix mutates the generated child routes' getParentRoute to point at federatedRootRoute. Safe because the federated ./App and the standalone/legacy entries don't run in the same runtime simultaneously (MF v2 loads only ./App). If you'd prefer to avoid any shared-object mutation, the alternative is generating the embedded tree from a Console-unique generated file — heavier; happy to switch.
  • A separate, cosmetic dev-only issue (embedded Console inherits Cloud UI's 14px root font-size, shrinking rem-based Tailwind spacing vs prod) is not in this PR — still under investigation.

…collision

Embedded Console (MF v2) renders Cloud UI's root route instead of its own
in dev, because both apps compile a module with the identical id
./src/routes/__root.tsx and the shared rsbuild/MF dev runtime resolves the
host's. That substitutes Cloud UI's RootComponent (react NuqsAdapter,
Builder.io <Content>, <CommandPalette>/KBar) as Console's embedded root,
which: crashes on useKBar (no KBarProvider in this subtree) -> empty
sidebar; and leaves Console's useQueryState consumers without a matching
nuqs adapter context -> NUQS-404.

Re-root the federated router onto Console's own federatedRootRoute (a
Console-unique module path that can't collide), and give it RouterSync +
RequireAuth (RequireAuth triggers the user-data fetch that gates the
endpoint-compatibility fetch and the embedded sidebar items). Standalone
(app.tsx/__root.tsx) and prod are unaffected.
@c-julin c-julin force-pushed the jc/fix-console-mf-v2-federated-root branch from c0705fc to 8aebc0c Compare June 16, 2026 14:56
The embedded MF-v2 Console renders federatedRootRoute, not __root.tsx's
RootLayout, so it was missing <DebugHelper /> — the component that
registers the Cmd+Shift+D debug dialog (dev-only). Render it in the
federated root too, gated on NODE_ENV=development, mirroring __root.tsx.
@c-julin

c-julin commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @c-julin's task in 2m 2s —— View job


Code Review — PR #2508: re-root federated Console router

  • Read changed files + surrounding context
  • Analyze root-cause reasoning & fix approach
  • Check for bugs / edge cases
  • Post review feedback

Thorough writeup — the root-cause analysis (host/remote ./src/routes/__root.tsx module-id collision in the shared MF v2 dev runtime) is convincing, and the fix is narrowly scoped to the federated path. Verified the mechanics against routeTree.gen.ts, __root.tsx, require-auth.tsx, and federated-providers.tsx. Overall this looks correct and well-documented. A few notes below.

Verified ✅

  • Re-parenting is mechanically sound. routeTree.children is the rootRouteChildren object (routeTree.gen.ts:1627/1703), so Object.values(...) yields the top-level routes. Only direct children's getParentRoute is remapped; nested routes (e.g. getParentRoute: () => SecurityRoute at routeTree.gen.ts:148) keep their parents intact. TanStack resolves parentRoute lazily from options.getParentRoute during router.init(), so overriding options.getParentRoute is the right hook.
  • Runs once at module load (const federatedRouteTree = createFederatedRouteTree()), not per-render — good.
  • Context type matches. FederatedRouterContext is structurally identical to RouterContext, and createRouter supplies the same { basePath, queryClient, dataplaneTransport }.
  • TooltipProvider is covered by FederatedProviders (federated-providers.tsx:38), so FederatedAppContent correctly omits it (unlike __root.tsx's AppContent).
  • RequireAuth / RouterSync parity with __root.tsx is correct, and RouterSync already no-ops its window-event dispatch in federated mode (router-sync.tsx:62-68), so no double navigation sync.

Worth confirming

  1. Missing <Toaster> in the federated layout. __root.tsx's shared AppContent renders <Toaster position="top-right" richColors /> (__root.tsx:116), but FederatedAppContent does not. Any code using sonner toast() won't surface notifications in MF v2 embedded mode. If the host doesn't provide a sonner <Toaster>, this is a silent functional gap. Is this intentional (host owns toasts), or should it be mirrored like the rest of EmbeddedLayout?

  2. Mutating shared route singletons. child.options.getParentRoute = () => federatedRootRoute mutates the generated route objects in place. You've correctly reasoned this is safe because ./App and the standalone/legacy entries don't share a runtime. The one residual edge case: if Cloud UI ever mounts two <ConsoleApp> instances in the same runtime, they share these mutated singletons — but that's a pre-existing property of passing the singleton routeTree to createRouter, not introduced here. No action needed; just flagging the assumption.

  3. Private API + array arg. _addFileChildren is an underscore-prefixed internal, and you pass an array here vs. the object map the generator uses. Both work today (TanStack normalizes children), but both are undocumented surfaces that could shift on a router upgrade. A one-line comment noting the reliance would help future maintainers.

Minor / cosmetic

  • __root.tsx's embedded AppContent wraps <Outlet /> in <div className="pt-8">; FederatedAppContent omits that top padding. Likely in the same bucket as the rem/font-size delta you've deferred — fine to leave, just noting the layout divergence.

No blocking issues from my read — items 1 and 2 are the ones worth a quick confirmation before merge.
· jc/fix-console-mf-v2-federated-root

Mirror __root.tsx's embedded AppContent in the MF-v2 federated layout
(which now renders in prod where enable-console-mf-v2 is on):
- Render <Toaster> — sonner is not an MF-shared singleton, so the host's
  Toaster can't surface Console's toasts; without this, toast() is silent
  in embedded mode.
- Wrap <Outlet> in the pt-8 spacing div to match AppContent.

(Fullscreen-route branch is intentionally omitted — no route sets
staticData.fullscreen on master; it lands with the SQL workspace.)
@malinskibeniamin malinskibeniamin merged commit b1dbe6c into master Jun 19, 2026
18 checks passed
@malinskibeniamin malinskibeniamin deleted the jc/fix-console-mf-v2-federated-root branch June 19, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants