Skip to content

feat: set up module federation - #40

Merged
jpvosloo merged 6 commits into
mainfrom
feat/module-federation
Jul 3, 2026
Merged

feat: set up module federation#40
jpvosloo merged 6 commits into
mainfrom
feat/module-federation

Conversation

@PotatoEater0127

@PotatoEater0127 PotatoEater0127 commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Set up module federation

Summary by CodeRabbit

  • New Features

    • Added module-federation support for the quest 3-tier, quest 5-tier, and quest 5-tier EG areas, enabling them to load as separate app sections.
    • Added loading states while those remote sections are being fetched.
  • Documentation

    • Updated local setup and development notes with new federation-based run commands and environment configuration guidance.

@coderabbitai

coderabbitai Bot commented Mar 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces three independent Module Federation remote UI apps (quest3Tier, quest5Tier, quest5TierEg), each with its own AppRoutes component, index.html, Vite federation config, and scripts. The host UI app is updated to lazy-load these remotes via new route wrappers, TypeScript module declarations, and updated routing/build configuration.

Changes

Module Federation for quest tier remotes

Layer / File(s) Summary
quest3Tier remote module
module/quest3Tier/ui/AppRoutes.tsx, module/quest3Tier/ui/index.html, module/quest3Tier/ui/package.json, module/quest3Tier/ui/vite.config.ts
Adds Quest3TierAppRoutes with a RouteModuleElement wrapper that runs optional clientLoaders, an HTML entry point, and federation build/dev scripts and Vite config exposing the routes as remoteEntry.js on port 5174.
quest5Tier remote module
module/quest5Tier/ui/AppRoutes.tsx, module/quest5Tier/ui/index.html, module/quest5Tier/ui/package.json, module/quest5Tier/ui/vite.config.ts
Mirrors the quest3Tier pattern for Quest5TierAppRoutes with federation config on port 5175.
quest5TierEg remote module
module/quest5TierEg/ui/AppRoutes.tsx, module/quest5TierEg/ui/index.html, module/quest5TierEg/ui/package.json, module/quest5TierEg/ui/vite.config.ts
Mirrors the same pattern for Quest5TierEgAppRoutes with federation config on port 5176.
Host remote route wiring and types
ui/app/routes.ts, ui/app/routes/remotes/*, ui/app/types/module-federation.d.ts
Removes direct quest tier route imports from ui/app/routes.ts in favor of wildcard routes pointing to new lazy-loaded Suspense route wrappers, and declares TypeScript module types for each federated AppRoutes export.
Host build/dev federation config and docs
ui/vite.config.ts, ui/package.json, ui/README.md
Converts the host Vite config to a callback that wires a federation host plugin with remote URLs and shared deps, adds dev:federation/mf:* scripts and dependency changes, and documents the new dev workflow.
Repository metadata
package.json
Adds a top-level packageManager field.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant HostRouter as Host Router (ui/app/routes.ts)
  participant RemoteRoute as Remote Route Wrapper
  participant RemoteAppRoutes as Remote AppRoutes (e.g. Quest3TierAppRoutes)
  participant RouteModuleElement
  participant ClientLoader as routeModule.clientLoader

  User->>HostRouter: navigate to /quest3Tier/*
  HostRouter->>RemoteRoute: match wildcard route
  RemoteRoute->>RemoteAppRoutes: React.lazy import remoteEntry.js
  RemoteAppRoutes-->>RemoteRoute: resolved component (Suspense)
  RemoteRoute->>RouteModuleElement: render matched sub-route
  RouteModuleElement->>ClientLoader: call clientLoader(params) if present
  ClientLoader-->>RouteModuleElement: loaderData
  RouteModuleElement-->>User: render default component with loaderData
Loading

Suggested reviewers: luke-yeh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing module federation support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/module-federation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PotatoEater0127
PotatoEater0127 force-pushed the feat/module-federation branch from 0fa5748 to 13b7200 Compare July 3, 2026 03:26
@PotatoEater0127
PotatoEater0127 marked this pull request as ready for review July 3, 2026 12:30
@jpvosloo
jpvosloo merged commit 577fbdd into main Jul 3, 2026
3 of 5 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (2)
ui/app/routes/remotes/Quest5TierEgRemote.tsx (1)

6-16: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Same missing error-boundary gap as the other remote wrappers.

Same concern raised on Quest3TierRemote.tsx: no ErrorBoundary around this Suspense/lazy pair, so a failed quest5TierEgRemote load will bubble up uncaught.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/app/routes/remotes/Quest5TierEgRemote.tsx` around lines 6 - 16, The
Quest5TierEgRemoteRoute wrapper currently renders a lazy-loaded remote inside
Suspense without any ErrorBoundary, so load failures will escape uncaught.
Update Quest5TierEgRemoteRoute to wrap the Quest5TierEgRoutes lazy import and
Suspense fallback with the same ErrorBoundary pattern used in the other remote
wrappers, ensuring a failed quest5TierEgRemote/AppRoutes load is handled
gracefully.
ui/app/routes/remotes/Quest5TierRemote.tsx (1)

6-16: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Same missing error-boundary gap as the other remote wrappers.

Same concern raised on Quest3TierRemote.tsx: no ErrorBoundary around this Suspense/lazy pair, so a failed quest5TierRemote load will bubble up uncaught.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/app/routes/remotes/Quest5TierRemote.tsx` around lines 6 - 16, Wrap the
lazy-loaded Quest5Tier remote in an ErrorBoundary like the other remote
wrappers, since the current Quest5TierRemoteRoute only uses Suspense and will
let failed quest5TierRemote/AppRoutes loads bubble uncaught. Update
Quest5TierRemoteRoute to render the Quest5TierRoutes inside the same
ErrorBoundary pattern used in the existing remote route components, keeping the
Suspense fallback unchanged.
🧹 Nitpick comments (8)
module/quest3Tier/ui/AppRoutes.tsx (1)

5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

@ts-nocheck disables type safety for the whole route module.

Given the file defines a typed FrameworkRouteModule contract that other route modules must satisfy, disabling type checking here removes the compiler's ability to catch mismatches (e.g., wrong clientLoader signature) before runtime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest3Tier/ui/AppRoutes.tsx` at line 5, Remove the blanket `@ts-nocheck`
from AppRoutes.tsx so the route module is type-checked again. Verify the module
still satisfies the FrameworkRouteModule contract and fix any resulting type
errors in the route exports, especially around functions like clientLoader and
other route-specific hooks, instead of suppressing them globally.
module/quest3Tier/ui/vite.config.ts (1)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Leftover workaround comment.

The comment flags a missing tsconfig.json as a known gap. Worth tracking before this ships more broadly across remotes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest3Tier/ui/vite.config.ts` at line 12, Remove the leftover
workaround note from the Vite JSX config in vite.config.ts; the jsx: "automatic"
setting in the config should remain, but the inline comment about lacking
tsconfig.json should be deleted or replaced with a proper tracked follow-up. Use
the vite.config.ts export/config block as the locator when updating the JSX
setting.
module/quest5Tier/ui/AppRoutes.tsx (2)

5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

// @ts-nocheck`` disables checking for a file that defines its own types.

The file declares FrameworkRouteModule with typed props yet disables TS entirely, defeating the purpose of the type contract with clientLoader/default.

Also applies to: 19-22

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest5Tier/ui/AppRoutes.tsx` at line 5, Remove the file-wide
ts-nocheck from AppRoutes.tsx so TypeScript can validate the module’s own type
contract. Keep the existing FrameworkRouteModule declaration and ensure
clientLoader and default continue to match its typed props, fixing any resulting
type errors instead of suppressing them.

1-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Identical RouteModuleElement/routing scaffold duplicated across three remotes.

This file is byte-for-byte identical to module/quest5TierEg/ui/AppRoutes.tsx (and reportedly quest3Tier). Given @zenmechat/shared-ui is already aliased in vite.config.ts, consider extracting RouteModuleElement and FrameworkRouteModule into that shared package to avoid triplicated logic that must be kept in sync across remotes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest5Tier/ui/AppRoutes.tsx` around lines 1 - 73, The routing scaffold
is duplicated across multiple remotes, so extract the shared
`FrameworkRouteModule` type and `RouteModuleElement` component from
`Quest5TierAppRoutes` into `@zenmechat/shared-ui` and import it here instead of
keeping a local copy. Update `Quest5TierAppRoutes` to only define the route
table and reuse the shared wrapper so the identical logic stays in sync across
`AppRoutes` implementations.
module/quest5Tier/ui/vite.config.ts (1)

32-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fragile relative-path alias for shared UI package.

fileURLToPath(new URL("../../../ui", import.meta.url)) hardcodes the remote's depth relative to the root ui folder; any future restructuring of module/quest5Tier/ui breaks this alias silently at build time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest5Tier/ui/vite.config.ts` at line 32, The "`@zenmechat/shared-ui`"
alias is using a fragile hardcoded relative path via fileURLToPath(new
URL("../../../ui", import.meta.url)), so it will break if the module layout
changes. Update the Vite alias in vite.config.ts to resolve the shared UI
location from a stable source used elsewhere in the repo, or centralize the path
resolution in a reusable helper, so the alias no longer depends on the current
folder depth.
ui/vite.config.ts (1)

19-19: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

shared list omits singleton/requiredVersion, risking duplicate React instances.

With @originjs/vite-plugin-federation, listing react/react-dom as bare strings does not guarantee a single shared instance between host and remotes; this is a documented root cause of "Invalid hook call" (React error #321) crashes in federated apps using this exact plugin. Configure the object form with singleton: true (and ideally requiredVersion) for react, react-dom, react-router, and react-router-dom.

🔧 Suggested fix
-    shared: ["react", "react-dom", "react-router", "react-router-dom"],
+    shared: {
+      react: { singleton: true, requiredVersion: "^19.0.0" },
+      "react-dom": { singleton: true, requiredVersion: "^19.0.0" },
+      "react-router": { singleton: true },
+      "react-router-dom": { singleton: true },
+    },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/vite.config.ts` at line 19, The shared federation config currently lists
react, react-dom, react-router, and react-router-dom as bare entries, which can
allow duplicate instances and trigger invalid hook call issues. Update the
shared configuration in vite.config.ts to use the object form for each of those
packages, setting singleton true and ideally requiredVersion, so the
ModuleFederation setup in the Vite config enforces a single shared React tree
across host and remotes.
ui/package.json (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

pnpm dlx concurrently is unpinned and network-dependent on every run.

Using dlx re-resolves/downloads concurrently each invocation (subject to pnpm's cache/registry availability), rather than using a pinned local devDependency. Prefer adding concurrently to devDependencies and invoking it via pnpm exec concurrently (or the local bin directly) for reproducible, offline-friendly dev tooling.

♻️ Suggested fix
-    "dev:federation": "pnpm dlx concurrently -k -n providers-watch,providers-preview,host -c cyan,magenta,green \"pnpm run mf:providers:watch\" \"pnpm run mf:providers:preview\" \"pnpm run mf:host:dev\"",
+    "dev:federation": "concurrently -k -n providers-watch,providers-preview,host -c cyan,magenta,green \"pnpm run mf:providers:watch\" \"pnpm run mf:providers:preview\" \"pnpm run mf:host:dev\"",

And add to devDependencies:

"concurrently": "^9.0.0"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/package.json` at line 11, The dev script uses an unpinned network-based
pnpm dlx invocation for concurrently, so make the tooling reproducible by adding
concurrently to devDependencies and updating the dev:federation script in
package.json to use the local binary via pnpm exec concurrently; this keeps the
existing mf:providers:watch, mf:providers:preview, and mf:host:dev commands
intact while removing the dlx dependency.
ui/README.md (1)

5-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New env.json instructions duplicate/precede the existing setup steps below.

Line 5 tells the reader to "Add env.json with proper data" as step 1, but the untouched steps further down (lines 46-51) separately instruct running pnpm i first, then copying env.json.example to env.json, then editing MSAL config values before setting env.json. Having two separate, differently-worded, restarted numbered lists for overlapping setup work is confusing — readers may skip the detailed steps thinking step 1 already covered it. Consider merging these into one consistent, sequential setup flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/README.md` around lines 5 - 51, The setup instructions in ui/README.md are
duplicated and the numbering restarts, which makes the onboarding flow unclear.
Consolidate the existing “Add env.json with proper data” step with the later
install/copy/edit/run steps into one sequential setup list, and make sure the
guidance around env.json, pnpm i, and editing msalInstance.ts is presented once
in a consistent order. Refer to the README’s setup section and the dev mode
instructions so the flow reads as a single process.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@module/quest3Tier/ui/AppRoutes.tsx`:
- Around line 30-52: In RouteModuleElement’s load effect, clientLoader can
reject or return a redirect Response, so loading may stay true and redirects may
be rendered as data. Update the load flow around routeModule.clientLoader in
AppRoutes.tsx to catch errors, always clear isLoading in a finally block, and
detect Response redirects before calling setLoaderData so navigation happens
instead of storing the response.

In `@module/quest3Tier/ui/vite.config.ts`:
- Line 21: The shared dependency setup in the Vite federation config is using
array shorthand, which can allow separate React and router instances to be
loaded. Update the shared config in the Vite federation settings to use object
entries for react, react-dom, react-router, and react-router-dom with singleton
enabled, and make sure their versions stay aligned with the other federation
configs so host and remote resolve the same instances.

In `@module/quest5Tier/ui/AppRoutes.tsx`:
- Around line 33-45: The load flow in AppRoutes.tsx leaves the route stuck when
routeModule.clientLoader rejects, because setIsLoading(false) is only reached on
success. Wrap the await in load() with try/catch/finally so isLoading is always
cleared, and surface the failure through the route’s existing error handling
instead of letting an unhandled rejection escape. Keep the fix localized to
load() and its use of routeModule.clientLoader, setLoaderData, and setIsLoading.

In `@module/quest5Tier/ui/index.html`:
- Around line 8-10: The federated UI entry currently only loads AppRoutes.tsx
and does not mount the app itself, so add a standalone bootstrap that calls
createRoot(...).render(...) and wraps the routes in an appropriate Router for
the module/quest5Tier/ui entry. Update the UI entry path referenced by
index.html so dev:federation and preview:federation render the app independently
while keeping the existing AppRoutes component as the route definition.

In `@module/quest5Tier/ui/vite.config.ts`:
- Line 21: The shared dependency setup currently uses array form, which can
allow separate React/router instances to be loaded; update the shared config in
the Vite setup to object form and mark react, react-dom, react-router, and
react-router-dom as singleton: true. Apply the same singleton sharing in both
this remote and the host so the quest-tier UI and existing remotes all resolve
to one React/router instance.

In `@ui/app/routes/remotes/Quest3TierRemote.tsx`:
- Around line 6-16: The Quest3TierRemoteRoute wrapper only uses Suspense around
Quest3TierRoutes, so a failed remote import can escape and crash the surrounding
layout. Wrap the lazy remote component in an ErrorBoundary inside
Quest3TierRemoteRoute and render a safe fallback when quest3TierRemote/AppRoutes
fails to load, using the existing Quest3TierRoutes symbol to locate the change.
Since Quest3TierRemote.tsx matches Quest5TierRemote.tsx and
Quest5TierEgRemote.tsx, consider extracting a shared createRemoteRoute(importFn,
moduleName) helper so the error-boundary handling is centralized across all
remote route wrappers.

In `@ui/package.json`:
- Line 44: The dependency setup is mixing vite@^7.3.6 with
`@originjs/vite-plugin-federation`@^1.4.1 even though that plugin does not support
Vite 7 yet. Update the federation tooling in the ui package by either replacing
`@originjs/vite-plugin-federation` with a Vite 7-compatible alternative or
downgrading/pinning vite to a supported version. Make the change in the package
manifest so the build uses a compatible vite + federation pairing.
- Around line 44-45: `@react-router/dev` is out of sync with the other React
Router packages in package.json; update the dependency to match the shared React
Router version used by `react-router`, `@react-router/node`, and
`@react-router/serve`. Keep the change limited to the package version entry so
all React Router packages stay on the same release train and remain compatible.

In `@ui/vite.config.ts`:
- Around line 14-18: The remote URL defaults in the defineConfig callback are
too permissive because they fall back to localhost during production builds as
well. Update the Vite config logic around the remotes object to use the
callback’s command/mode context and only allow localhost fallbacks in
development; for production build paths, require the VITE_QUEST3TIER_REMOTE_URL,
VITE_QUEST5TIER_REMOTE_URL, and VITE_QUEST5TIEREG_REMOTE_URL env values and fail
loudly if any are missing. Use the existing remote keys quest3TierRemote,
quest5TierRemote, and quest5TierEgRemote to locate the change.

---

Duplicate comments:
In `@ui/app/routes/remotes/Quest5TierEgRemote.tsx`:
- Around line 6-16: The Quest5TierEgRemoteRoute wrapper currently renders a
lazy-loaded remote inside Suspense without any ErrorBoundary, so load failures
will escape uncaught. Update Quest5TierEgRemoteRoute to wrap the
Quest5TierEgRoutes lazy import and Suspense fallback with the same ErrorBoundary
pattern used in the other remote wrappers, ensuring a failed
quest5TierEgRemote/AppRoutes load is handled gracefully.

In `@ui/app/routes/remotes/Quest5TierRemote.tsx`:
- Around line 6-16: Wrap the lazy-loaded Quest5Tier remote in an ErrorBoundary
like the other remote wrappers, since the current Quest5TierRemoteRoute only
uses Suspense and will let failed quest5TierRemote/AppRoutes loads bubble
uncaught. Update Quest5TierRemoteRoute to render the Quest5TierRoutes inside the
same ErrorBoundary pattern used in the existing remote route components, keeping
the Suspense fallback unchanged.

---

Nitpick comments:
In `@module/quest3Tier/ui/AppRoutes.tsx`:
- Line 5: Remove the blanket `@ts-nocheck` from AppRoutes.tsx so the route module
is type-checked again. Verify the module still satisfies the
FrameworkRouteModule contract and fix any resulting type errors in the route
exports, especially around functions like clientLoader and other route-specific
hooks, instead of suppressing them globally.

In `@module/quest3Tier/ui/vite.config.ts`:
- Line 12: Remove the leftover workaround note from the Vite JSX config in
vite.config.ts; the jsx: "automatic" setting in the config should remain, but
the inline comment about lacking tsconfig.json should be deleted or replaced
with a proper tracked follow-up. Use the vite.config.ts export/config block as
the locator when updating the JSX setting.

In `@module/quest5Tier/ui/AppRoutes.tsx`:
- Line 5: Remove the file-wide ts-nocheck from AppRoutes.tsx so TypeScript can
validate the module’s own type contract. Keep the existing FrameworkRouteModule
declaration and ensure clientLoader and default continue to match its typed
props, fixing any resulting type errors instead of suppressing them.
- Around line 1-73: The routing scaffold is duplicated across multiple remotes,
so extract the shared `FrameworkRouteModule` type and `RouteModuleElement`
component from `Quest5TierAppRoutes` into `@zenmechat/shared-ui` and import it
here instead of keeping a local copy. Update `Quest5TierAppRoutes` to only
define the route table and reuse the shared wrapper so the identical logic stays
in sync across `AppRoutes` implementations.

In `@module/quest5Tier/ui/vite.config.ts`:
- Line 32: The "`@zenmechat/shared-ui`" alias is using a fragile hardcoded
relative path via fileURLToPath(new URL("../../../ui", import.meta.url)), so it
will break if the module layout changes. Update the Vite alias in vite.config.ts
to resolve the shared UI location from a stable source used elsewhere in the
repo, or centralize the path resolution in a reusable helper, so the alias no
longer depends on the current folder depth.

In `@ui/package.json`:
- Line 11: The dev script uses an unpinned network-based pnpm dlx invocation for
concurrently, so make the tooling reproducible by adding concurrently to
devDependencies and updating the dev:federation script in package.json to use
the local binary via pnpm exec concurrently; this keeps the existing
mf:providers:watch, mf:providers:preview, and mf:host:dev commands intact while
removing the dlx dependency.

In `@ui/README.md`:
- Around line 5-51: The setup instructions in ui/README.md are duplicated and
the numbering restarts, which makes the onboarding flow unclear. Consolidate the
existing “Add env.json with proper data” step with the later
install/copy/edit/run steps into one sequential setup list, and make sure the
guidance around env.json, pnpm i, and editing msalInstance.ts is presented once
in a consistent order. Refer to the README’s setup section and the dev mode
instructions so the flow reads as a single process.

In `@ui/vite.config.ts`:
- Line 19: The shared federation config currently lists react, react-dom,
react-router, and react-router-dom as bare entries, which can allow duplicate
instances and trigger invalid hook call issues. Update the shared configuration
in vite.config.ts to use the object form for each of those packages, setting
singleton true and ideally requiredVersion, so the ModuleFederation setup in the
Vite config enforces a single shared React tree across host and remotes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3c8cff4-ecea-4cc4-ab4b-e2867a4d4254

📥 Commits

Reviewing files that changed from the base of the PR and between 96213a7 and 9f0155a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • module/quest3Tier/ui/AppRoutes.tsx
  • module/quest3Tier/ui/index.html
  • module/quest3Tier/ui/package.json
  • module/quest3Tier/ui/vite.config.ts
  • module/quest5Tier/ui/AppRoutes.tsx
  • module/quest5Tier/ui/index.html
  • module/quest5Tier/ui/package.json
  • module/quest5Tier/ui/vite.config.ts
  • module/quest5TierEg/ui/AppRoutes.tsx
  • module/quest5TierEg/ui/index.html
  • module/quest5TierEg/ui/package.json
  • module/quest5TierEg/ui/vite.config.ts
  • package.json
  • ui/README.md
  • ui/app/routes.ts
  • ui/app/routes/remotes/Quest3TierRemote.tsx
  • ui/app/routes/remotes/Quest5TierEgRemote.tsx
  • ui/app/routes/remotes/Quest5TierRemote.tsx
  • ui/app/types/module-federation.d.ts
  • ui/package.json
  • ui/vite.config.ts

Comment on lines +30 to +52
useEffect(() => {
let isMounted = true;

const load = async () => {
if (!routeModule.clientLoader) {
setIsLoading(false);
return;
}

setIsLoading(true);
const nextData = await routeModule.clientLoader({ params });
if (isMounted) {
setLoaderData(nextData);
setIsLoading(false);
}
};

load();

return () => {
isMounted = false;
};
}, [routeModule, location.pathname, location.search, params]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== AppRoutes.tsx ==\n'
sed -n '1,220p' module/quest3Tier/ui/AppRoutes.tsx

printf '\n== QuestionDetailAdd.tsx ==\n'
sed -n '1,220p' module/quest3Tier/ui/routes/QuestionDetailAdd.tsx

printf '\n== Search for clientLoader / redirect usage in quest3Tier UI ==\n'
rg -n "clientLoader|redirect\(" module/quest3Tier/ui

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 11384


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== QuestionCombinationList.tsx ==\n'
sed -n '1,120p' module/quest3Tier/ui/routes/QuestionCombinationList.tsx

printf '\n== AnswerQuestion2.tsx ==\n'
sed -n '1,140p' module/quest3Tier/ui/routes/AnswerQuestion2.tsx

printf '\n== Any other redirect/error patterns in quest3Tier routes ==\n'
rg -n "throw new Error|return redirect\(" module/quest3Tier/ui/routes

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 10119


Handle loader errors and redirect responses in RouteModuleElement (module/quest3Tier/ui/AppRoutes.tsx:34-45) await routeModule.clientLoader(...) can reject and leave isLoading stuck on true, and a returned redirect() response is currently stored as loaderData instead of navigating. Catch failures, clear loading in a finally, and special-case Response redirects before rendering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest3Tier/ui/AppRoutes.tsx` around lines 30 - 52, In
RouteModuleElement’s load effect, clientLoader can reject or return a redirect
Response, so loading may stay true and redirects may be rendered as data. Update
the load flow around routeModule.clientLoader in AppRoutes.tsx to catch errors,
always clear isLoading in a finally block, and detect Response redirects before
calling setLoaderData so navigation happens instead of storing the response.

exposes: {
"./AppRoutes": "./AppRoutes.tsx",
},
shared: ["react", "react-dom", "react-router", "react-router-dom"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

@originjs/vite-plugin-federation shared singleton react

💡 Result:

In @originjs/vite-plugin-federation, the singleton property for shared dependencies is a point of significant community discussion and inconsistent behavior due to changes in the plugin's internal implementation [1][2][3]. While many developers continue to include singleton: true in their configuration for dependencies like react and react-dom [1][4], its effectiveness is not guaranteed in recent versions of the plugin [2][3]. Users have reported that shared dependencies are sometimes loaded multiple times despite setting singleton: true [1][2]. Key considerations regarding shared singletons and React: 1. Implementation Status: The singleton property was removed from or commented out in the plugin's type definitions in certain versions [1][2]. Whether it remains functional depends on the specific version of the plugin being used; some users report it works in earlier versions (e.g., 1.1.8) but breaks in later releases (e.g., 1.2.x) [2][3]. 2. Alternative Configurations: When encountering multiple instances of React, some users have found success by adjusting their Vite configuration—specifically, setting the JSX runtime to classic instead of automatic [2]. vite.config.js plugins: [ react({ jsxRuntime: 'classic' }) ] 3. Versioning Issues: Ensure that host and remote applications are using compatible versions of shared dependencies [5][6]. Mismatched versions can lead the plugin to fallback to local copies, effectively ignoring the singleton requirement [5][6]. 4. Ecosystem Limitations: The official documentation does not explicitly detail the current status of the singleton flag, and the maintainers have acknowledged challenges with supporting shared dependencies—particularly when mixing Vite and Webpack, or when handling CommonJS modules [5][3][4]. If you are facing issues with multiple React instances, consider checking your plugin version and verifying that both host and remote are configured with identical shared dependencies and version requirements [1][5][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant Vite configs and the federation plugin version.
git ls-files 'module/quest3Tier/ui/vite.config.ts' 'ui/vite.config.ts' '**/package.json' '**/pnpm-lock.yaml' '**/package-lock.json' '**/yarn.lock' | sed -n '1,200p'

printf '\n--- vite.config.ts files ---\n'
for f in module/quest3Tier/ui/vite.config.ts ui/vite.config.ts; do
  if [ -f "$f" ]; then
    echo "### $f"
    cat -n "$f" | sed -n '1,220p'
    echo
  fi
done

printf '\n--- package.json references ---\n'
rg -n '"`@originjs/vite-plugin-federation`"|singleton|react-router|react-dom|react-router"' -g 'package.json' -g 'pnpm-lock.yaml' -g 'package-lock.json' -g 'yarn.lock' -g 'vite.config.ts' .

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 31200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the package metadata for the federation plugin version in the workspace that owns the file.
fd -a 'package.json' .

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 954


🌐 Web query:

@originjs/vite-plugin-federation 1.4.1 singleton shared react documentation

💡 Result:

In @originjs/vite-plugin-federation, support for the singleton property in the shared configuration has been inconsistent and subject to changes in internal type definitions [1][2]. While earlier versions supported it explicitly, subsequent updates led to reports where the property appeared ignored or missing from TypeScript definitions, even if functionality remained for some users [1][2]. If you are using @originjs/vite-plugin-federation and encountering issues with multiple React instances: 1. Configuration: You can attempt to define singleton in your shared object as follows [1][3]: shared: { react: { singleton: true, requiredVersion: "^18.3.1" }, 'react-dom': { singleton: true, requiredVersion: "^18.3.1" } } 2. Compatibility Notes: Users have reported that issues with multiple React instances may persist due to how Vite/Rollup handles shared modules versus Webpack [4][3]. Some developers have mitigated this by using the 'classic' JSX runtime in @vitejs/plugin-react instead of the automatic runtime to resolve hydration or instance-duplication errors [2]. 3. Alternative Plugin: If you require robust, well-documented support for Module Federation in Vite, consider the @module-federation/vite plugin (often used with Nx or standalone) [5][6]. This plugin explicitly documents and maintains first-class support for singleton: true in shared dependency configurations [5][7]. Note: Ensure you do not confuse @originjs/vite-plugin-federation with newer community plugins under the @module-federation scope, which offer more standardized API support for modern Module Federation features [8][5][7].

Citations:


🌐 Web query:

site:github.com/originjs/vite-plugin-federation singleton shared react 1.4.1

💡 Result:

In originjs/vite-plugin-federation, the latest release shown on GitHub is v1.4.1 (Apr 12, 2025). The README documents shared for dependencies like React, and warns that mixing Vite and Webpack in React projects can cause shared chunk issues. (github.com)

For React specifically, a GitHub issue reports that sharing react, react-dom, and react-router-dom can still create multiple instances, and that adding singleton: true was the workaround in both host and remote configs. (github.com)

If you want, I can turn that into the exact vite.config.js config snippet for host/remote.

Citations:


Use singleton shared deps for React and router. The array shorthand here can still let host and remote load separate React/router instances; switch these entries to object form with singleton: true and keep the versions aligned across all federation configs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest3Tier/ui/vite.config.ts` at line 21, The shared dependency setup
in the Vite federation config is using array shorthand, which can allow separate
React and router instances to be loaded. Update the shared config in the Vite
federation settings to use object entries for react, react-dom, react-router,
and react-router-dom with singleton enabled, and make sure their versions stay
aligned with the other federation configs so host and remote resolve the same
instances.

Comment on lines +33 to +45
const load = async () => {
if (!routeModule.clientLoader) {
setIsLoading(false);
return;
}

setIsLoading(true);
const nextData = await routeModule.clientLoader({ params });
if (isMounted) {
setLoaderData(nextData);
setIsLoading(false);
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unhandled clientLoader rejection leaves the route stuck loading forever.

Per the upstream AnswerQuestion.tsx clientLoader, loaders can throw new Response(...) on invalid/missing input. load() has no try/catch, so a thrown rejection never reaches setIsLoading(false), leaves isLoading true, and the component renders null indefinitely with no error surfaced to the user (also produces an unhandled promise rejection).

🐛 Proposed fix
     const load = async () => {
       if (!routeModule.clientLoader) {
         setIsLoading(false);
         return;
       }
 
       setIsLoading(true);
-      const nextData = await routeModule.clientLoader({ params });
-      if (isMounted) {
-        setLoaderData(nextData);
-        setIsLoading(false);
-      }
+      try {
+        const nextData = await routeModule.clientLoader({ params });
+        if (isMounted) {
+          setLoaderData(nextData);
+        }
+      } catch (error) {
+        console.error("Route loader failed:", error);
+        if (isMounted) {
+          setLoaderData(undefined);
+        }
+      } finally {
+        if (isMounted) {
+          setIsLoading(false);
+        }
+      }
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const load = async () => {
if (!routeModule.clientLoader) {
setIsLoading(false);
return;
}
setIsLoading(true);
const nextData = await routeModule.clientLoader({ params });
if (isMounted) {
setLoaderData(nextData);
setIsLoading(false);
}
};
const load = async () => {
if (!routeModule.clientLoader) {
setIsLoading(false);
return;
}
setIsLoading(true);
try {
const nextData = await routeModule.clientLoader({ params });
if (isMounted) {
setLoaderData(nextData);
}
} catch (error) {
console.error("Route loader failed:", error);
if (isMounted) {
setLoaderData(undefined);
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest5Tier/ui/AppRoutes.tsx` around lines 33 - 45, The load flow in
AppRoutes.tsx leaves the route stuck when routeModule.clientLoader rejects,
because setIsLoading(false) is only reached on success. Wrap the await in load()
with try/catch/finally so isLoading is always cleared, and surface the failure
through the route’s existing error handling instead of letting an unhandled
rejection escape. Keep the fix localized to load() and its use of
routeModule.clientLoader, setLoaderData, and setIsLoading.

Comment on lines +8 to +10
<body>
<script type="module" src="./AppRoutes.tsx"></script>
</body>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does @originjs/vite-plugin-federation remote index.html need its own ReactDOM render entry, or does the host always own mounting?

💡 Result:

In the Module Federation architecture utilized by @originjs/vite-plugin-federation, the host application (or shell) always owns the mounting process [1][2]. A remote application does not need its own ReactDOM render entry (i.e., it should not have its own self-contained root mount point) when running as a federated module [1]. Instead, the standard pattern is as follows: 1. Host Ownership: The host application acts as the shell and is responsible for defining the page structure, routing, and providing the mount point for remote components [1][2]. 2. Remote Role: Remote applications act as providers of components, routes, or modules [1][2]. They expose these assets via the plugin, which are then imported and rendered by the host [3][1]. 3. Rendering: The host imports the remote component (typically using dynamic imports like React.lazy) and renders it within its own component tree [4][1]. The remote component mounts into the DOM elements managed by the host [1]. If a remote app has its own index.html with a dedicated ReactDOM render entry, that entry is only intended for when the remote is run in standalone mode (e.g., for development or testing in isolation) [5]. When loaded as a federated remote, that standalone entry point is bypassed; attempting to mount a remote independently within a host's page—or having multiple competing render entries—can cause conflicts, blank screens, or shared dependency initialization issues [4][6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'ROOT FILES:\n'
git ls-files | sed -n '1,200p' | grep -E '^(module/quest5Tier/ui/|package.json|vite\.config|tsconfig|src/|module/quest5Tier/)'

printf '\nINDEX.HTML:\n'
cat -n module/quest5Tier/ui/index.html

printf '\nAPPROUTES SEARCH:\n'
rg -n "useRoutes|createRoot|ReactDOM|Router|BrowserRouter|HashRouter|Routes|Route|useParams|useLocation" module/quest5Tier/ui -S

printf '\nPACKAGE.JSON:\n'
cat -n package.json

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'FILE TREE (quest5Tier ui):\n'
find module/quest5Tier/ui -maxdepth 2 -type f | sort

printf '\nOUTLINE AppRoutes.tsx:\n'
ast-grep outline module/quest5Tier/ui/AppRoutes.tsx --view expanded || true

printf '\nREAD AppRoutes.tsx:\n'
cat -n module/quest5Tier/ui/AppRoutes.tsx

printf '\nREAD main-ish entry files:\n'
for f in module/quest5Tier/ui/main.tsx module/quest5Tier/ui/main.ts module/quest5Tier/ui/index.tsx module/quest5Tier/ui/index.ts; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f"
  fi
done

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 4496


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'SEARCH FOR VITE FEDERATION CONFIG / STANDALONE ENTRY:\n'
rg -n "federation|exposes|remotes|dev:federation|preview:federation|standalone|index.html|createRoot|hydrateRoot" -S . --glob '!**/node_modules/**'

printf '\nSEARCH index.html scripts in ui:\n'
rg -n "<script|type=\"module\"" module/quest5Tier/ui/index.html -n -S

printf '\nCHECK if AppRoutes.tsx imports ReactDOM or Router providers:\n'
python3 - <<'PY'
from pathlib import Path
p = Path("module/quest5Tier/ui/AppRoutes.tsx")
text = p.read_text()
for needle in ["ReactDOM", "createRoot", "BrowserRouter", "HashRouter", "RouterProvider", "useRoutes", "useParams", "useLocation"]:
    print(needle, needle in text)
PY

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 11796


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- module/quest5Tier/ui/package.json ---'
cat -n module/quest5Tier/ui/package.json

echo
echo '--- module/quest5Tier/ui/vite.config.ts ---'
cat -n module/quest5Tier/ui/vite.config.ts

echo
echo '--- module/quest5Tier/ui/README.md (if present) ---'
if [ -f module/quest5Tier/ui/README.md ]; then
  cat -n module/quest5Tier/ui/README.md
else
  echo '(absent)'
fi

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 3894


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- host route references to quest5Tier remote ---'
rg -n "quest5Tier|Quest5TierAppRoutes|AppRoutes" ui module/quest5Tier -S

echo
echo '--- module/quest5Tier/ui/vite.config.ts (imports and exposes) ---'
ast-grep outline module/quest5Tier/ui/vite.config.ts --view expanded || true
cat -n module/quest5Tier/ui/vite.config.ts

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 8115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("module/quest5Tier/ui/vite.config.ts")
text = p.read_text()
for needle in ["exposes", "remotes", "build:federation", "preview:federation", "dev:federation", "base", "server", "preview"]:
    idx = text.find(needle)
    print(needle, idx)
PY

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- module/quest5Tier/ui/package.json ---'
cat -n module/quest5Tier/ui/package.json

echo
echo '--- module/quest5Tier/ui/vite.config.ts ---'
cat -n module/quest5Tier/ui/vite.config.ts

echo
echo '--- host / remote references ---'
rg -n "quest5Tier|Quest5TierAppRoutes|AppRoutes" ui module/quest5Tier -S

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 10422


Add a standalone mount for the federated UI. module/quest5Tier/ui/index.html only loads AppRoutes.tsx; it never calls createRoot(...).render(...) or provides a Router, so dev:federation/preview:federation will open to a blank page and router hooks can fail when run outside the host.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest5Tier/ui/index.html` around lines 8 - 10, The federated UI entry
currently only loads AppRoutes.tsx and does not mount the app itself, so add a
standalone bootstrap that calls createRoot(...).render(...) and wraps the routes
in an appropriate Router for the module/quest5Tier/ui entry. Update the UI entry
path referenced by index.html so dev:federation and preview:federation render
the app independently while keeping the existing AppRoutes component as the
route definition.

exposes: {
"./AppRoutes": "./AppRoutes.tsx",
},
shared: ["react", "react-dom", "react-router", "react-router-dom"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files matching vite.config.ts ==\n'
git ls-files '**/vite.config.ts'

printf '\n== Relevant configs in module/quest5Tier/ui ==\n'
ast-grep outline module/quest5Tier/ui/vite.config.ts --view expanded || true
cat -n module/quest5Tier/ui/vite.config.ts | sed -n '1,120p'

printf '\n== Search for shared federation configs ==\n'
rg -n --glob 'vite.config.ts' 'shared\s*:' module ui . || true
rg -n --glob 'vite.config.ts' '`@originjs/vite-plugin-federation`|singleton|requiredVersion|shared\s*:\s*\[' module ui . || true

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 4272


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Package manifests ==\n'
git ls-files 'package.json' '**/package.json'

printf '\n== React/router dependency declarations ==\n'
for f in package.json ui/package.json module/quest3Tier/ui/package.json module/quest5Tier/ui/package.json module/quest5TierEg/ui/package.json; do
  if [ -f "$f" ]; then
    echo "--- $f"
    cat "$f" | python3 - <<'PY'
import json,sys
data=json.load(sys.stdin)
for section in ("dependencies","devDependencies","peerDependencies"):
    deps=data.get(section,{})
    hits={k:v for k,v in deps.items() if k in {"react","react-dom","react-router","react-router-dom","`@originjs/vite-plugin-federation`"}}
    if hits:
        print(section, hits)
PY
  fi
done

printf '\n== Any federation singleton examples ==\n'
rg -n 'singleton\s*:\s*true|requiredVersion\s*:\s*' . || true

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 1495


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Package manifests ==\n'
git ls-files 'package.json' '**/package.json'

printf '\n== React/router dependency declarations ==\n'
for f in package.json ui/package.json module/quest3Tier/ui/package.json module/quest5Tier/ui/package.json module/quest5TierEg/ui/package.json; do
  if [ -f "$f" ]; then
    echo "--- $f"
    python3 - <<'PY' "$f"
import json,sys
path=sys.argv[1]
with open(path) as fh:
    data=json.load(fh)
for section in ("dependencies","devDependencies","peerDependencies"):
    deps=data.get(section,{})
    hits={k:v for k,v in deps.items() if k in {"react","react-dom","react-router","react-router-dom","`@originjs/vite-plugin-federation`"}}
    if hits:
        print(section, hits)
PY
  fi
done

printf '\n== Any federation singleton examples ==\n'
rg -n 'singleton\s*:\s*true|requiredVersion\s*:\s*' . || true

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 1660


🌐 Web query:

@originjs/vite-plugin-federation shared array singleton requiredVersion docs

💡 Result:

In vite-plugin-federation, the shared configuration allows you to define dependencies shared between host and remote modules using either an array or an object [1][2]. When using an object configuration, each shared package can be defined with specific settings, including singleton and requiredVersion [1][2]. singleton: boolean When set to true, this option ensures that only one instance of the shared module is used across the host and all remotes [1][2]. This is commonly required for libraries that maintain internal state, such as React, to prevent issues like multiple instances of hooks (e.g., TypeError: Cannot read properties of null (reading 'useState')) [3][4]. requiredVersion: string This setting specifies the version range required from the host [5][6]. It is primarily used by the remote side [5][6]. If the version of the module provided by the host does not meet the specified requiredVersion, the remote will fall back to using its own bundled version, provided that import is not set to false [5][6]. Example configuration: shared: { react: { singleton: true, requiredVersion: '^18.3.1', }, 'react-dom': { singleton: true, requiredVersion: '^18.3.1', }, } Note: While older versions or specific forks (such as the jskits/vite-plugin-federation) explicitly document these fields in their API, users of the original @originjs/vite-plugin-federation have also reported using the singleton option to resolve dependency conflicts [3][4]. Always ensure your implementation matches the specific version of the plugin you are using [7].

Citations:


Use singleton shared deps for React/router
Switch shared to object form and mark react, react-dom, react-router, and react-router-dom as singleton: true in this remote and the host. The current array form can load separate router copies, and this repo already mixes router versions between ui and the quest-tier remotes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/quest5Tier/ui/vite.config.ts` at line 21, The shared dependency setup
currently uses array form, which can allow separate React/router instances to be
loaded; update the shared config in the Vite setup to object form and mark
react, react-dom, react-router, and react-router-dom as singleton: true. Apply
the same singleton sharing in both this remote and the host so the quest-tier UI
and existing remotes all resolve to one React/router instance.

Comment on lines +6 to +16
import { Suspense, lazy } from "react";

const Quest3TierRoutes = lazy(() => import("quest3TierRemote/AppRoutes"));

export default function Quest3TierRemoteRoute() {
return (
<Suspense fallback={<div>Loading quest3Tier module...</div>}>
<Quest3TierRoutes />
</Suspense>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing error boundary for remote-load failures.

lazy(() => import("quest3TierRemote/AppRoutes")) will reject if the remote is unreachable, deployed independently and out of sync, or returns a stale/broken remoteEntry.js. Federated remotes are a network dependency, so failures are expected in production. Without an ErrorBoundary, the rejection propagates past Suspense and can crash the entire protected layout, taking down unrelated routes rendered alongside it.

🛡️ Suggested fix
-import { Suspense, lazy } from "react";
+import { Suspense, lazy } from "react";
+import { RemoteErrorBoundary } from "../../components/RemoteErrorBoundary"; // or any ErrorBoundary impl

 const Quest3TierRoutes = lazy(() => import("quest3TierRemote/AppRoutes"));

 export default function Quest3TierRemoteRoute() {
   return (
-    <Suspense fallback={<div>Loading quest3Tier module...</div>}>
-      <Quest3TierRoutes />
-    </Suspense>
+    <RemoteErrorBoundary fallback={<div>quest3Tier module unavailable</div>}>
+      <Suspense fallback={<div>Loading quest3Tier module...</div>}>
+        <Quest3TierRoutes />
+      </Suspense>
+    </RemoteErrorBoundary>
   );
 }

Also, this wrapper is identical in shape to Quest5TierRemote.tsx and Quest5TierEgRemote.tsx. Consider a small factory (e.g., createRemoteRoute(importFn, moduleName)) to remove the duplication and centralize the error-boundary fix above.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/app/routes/remotes/Quest3TierRemote.tsx` around lines 6 - 16, The
Quest3TierRemoteRoute wrapper only uses Suspense around Quest3TierRoutes, so a
failed remote import can escape and crash the surrounding layout. Wrap the lazy
remote component in an ErrorBoundary inside Quest3TierRemoteRoute and render a
safe fallback when quest3TierRemote/AppRoutes fails to load, using the existing
Quest3TierRoutes symbol to locate the change. Since Quest3TierRemote.tsx matches
Quest5TierRemote.tsx and Quest5TierEgRemote.tsx, consider extracting a shared
createRemoteRoute(importFn, moduleName) helper so the error-boundary handling is
centralized across all remote route wrappers.

Comment thread ui/package.json
"devDependencies": {
"@eslint/js": "^9.38.0",
"@react-router/dev": "^7.15.1",
"@originjs/vite-plugin-federation": "^1.4.1",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd package.json --exec cat {} \; 2>/dev/null | rg -n '"vite"|"`@originjs/vite-plugin-federation`"'

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 656


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the UI manifest and any lockfile references for the federation plugin.
sed -n '1,240p' ui/package.json
printf '\n--- lockfile hits ---\n'
rg -n '"`@originjs/vite-plugin-federation`"|vite-plugin-federation|peerDependencies|vite": "\^7' -S --glob '!**/node_modules/**' .

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 15113


🌐 Web query:

@originjs/vite-plugin-federation peerDependencies vite 7 compatibility npm registry

💡 Result:

As of July 3, 2026, the @originjs/vite-plugin-federation package does not have official compatibility for Vite 7 [1]. The latest version of the plugin, 1.4.1, was published on April 12, 2025 [2][3]. Vite 7 was officially released on June 24, 2025 [4][5][6]. Because the plugin has not been updated since April 2025, it lacks explicit support for Vite 7 in its peerDependencies [2][3]. An open issue, "Vite 7 Support" (#732), was opened on the project's GitHub repository on January 23, 2026, to track this requirement [1]. Users attempting to use this plugin with Vite 7 may encounter peer dependency warnings or compatibility issues due to this lack of official updates [1].

Citations:


@originjs/vite-plugin-federation still lacks Vite 7 support

ui/package.json:44 pins @originjs/vite-plugin-federation@^1.4.1 alongside vite@^7.3.6, but the plugin does not advertise Vite 7 compatibility yet. That leaves federation builds at risk of breakage or invalid remoteEntry output. Consider switching to a Vite 7-compatible federation plugin or pinning Vite to a supported version.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/package.json` at line 44, The dependency setup is mixing vite@^7.3.6 with
`@originjs/vite-plugin-federation`@^1.4.1 even though that plugin does not support
Vite 7 yet. Update the federation tooling in the ui package by either replacing
`@originjs/vite-plugin-federation` with a Vite 7-compatible alternative or
downgrading/pinning vite to a supported version. Make the change in the package
manifest so the build uses a compatible vite + federation pairing.

Comment thread ui/package.json
Comment on lines +44 to +45
"@originjs/vite-plugin-federation": "^1.4.1",
"@react-router/dev": "^7.6.2",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for version skew across `@react-router/`* packages in this package.json
rg -n '"react-router"|"`@react-router/`' ui/package.json

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 319


🏁 Script executed:

sed -n '1,120p' ui/package.json

Repository: ZenMe-AU/ZBReactArchitecture

Length of output: 2644


🌐 Web query:

React Router package versions react-router @react-router/dev same version lockstep official docs

💡 Result:

Yes, the React Router packages, including react-router and @react-router/dev, are maintained in lockstep and should be kept at the same version [1][2][3]. The official React Router changelogs and release notes consistently demonstrate that when a new version is released, the primary packages (such as react-router, @react-router/dev, @react-router/node, and @react-router/serve) are updated to the same version number simultaneously [1][3]. For example, the release notes explicitly list updated dependencies for all related packages to match the current release version [1][3]. To ensure compatibility and avoid unexpected errors, it is standard practice to synchronize the versions of these packages in your package.json file. If you are managing your dependencies manually, you should ensure that both react-router and @react-router/dev are set to the same version [2].

Citations:


🌐 Web query:

Official React Router docs versioning @react-router/dev react-router same minor version

💡 Result:

In the React Router ecosystem, particularly since the release of v7, the @react-router/dev package and the core react-router package are maintained with strict version parity [1][2]. Official release notes and changelogs consistently show that these packages are updated together within the same version numbers (e.g., v7.17.0, v7.16.0) to ensure compatibility and stability [1][2][3]. When managing dependencies in your project, it is standard practice and highly recommended to keep @react-router/dev and react-router at the exact same minor (and patch) version to avoid potential issues arising from API mismatches or internal dependencies [4][1][2]. Key points regarding React Router versioning: 1. Version Parity: The core packages, including react-router, @react-router/dev, and others like @react-router/node or @react-router/serve, are published as a monorepo and released with identical version numbers [1][2][5]. 2. Dependency Management: You should align your package.json dependencies to match these versions. For example, if you are using react-router version 7.17.0, your @react-router/dev dependency should also be set to 7.17.0 [4][1]. 3. Upgrading: When upgrading, always update these packages to the same target version [1][3]. You can verify the latest matching versions by checking the official React Router changelog [6][3].

Citations:


Align @react-router/dev with the other React Router packages. react-router, @react-router/node, and @react-router/serve are on 7.15.1, but @react-router/dev is still on 7.6.2. These packages are released in lockstep, so this mismatch can cause build/runtime incompatibilities.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/package.json` around lines 44 - 45, `@react-router/dev` is out of sync
with the other React Router packages in package.json; update the dependency to
match the shared React Router version used by `react-router`,
`@react-router/node`, and `@react-router/serve`. Keep the change limited to the
package version entry so all React Router packages stay on the same release
train and remain compatible.

Comment thread ui/vite.config.ts
Comment on lines +14 to +18
remotes: {
quest3TierRemote: process.env.VITE_QUEST3TIER_REMOTE_URL ?? "http://localhost:5174/assets/remoteEntry.js",
quest5TierRemote: process.env.VITE_QUEST5TIER_REMOTE_URL ?? "http://localhost:5175/assets/remoteEntry.js",
quest5TierEgRemote: process.env.VITE_QUEST5TIEREG_REMOTE_URL ?? "http://localhost:5176/assets/remoteEntry.js",
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remote URLs silently default to localhost in production builds too.

The defineConfig callback receives { command, mode } but this code ignores them, so process.env.VITE_QUEST3TIER_REMOTE_URL ?? "http://localhost:5174/..." falls back to localhost even during vite build if the env var isn't set at build time. A production build without the env vars configured would silently ship a host pointing at developer localhost URLs instead of failing loudly.

🔧 Suggested fix
-export default defineConfig(() => {
+export default defineConfig(({ command }) => {
+  const isBuild = command === "build";
+  const requireEnv = (key: string, devDefault: string) => {
+    const value = process.env[key];
+    if (!value && isBuild) {
+      throw new Error(`Missing required env var ${key} for production build`);
+    }
+    return value ?? devDefault;
+  };
   const mfPlugin = federation({
     name: "uiHost",
     remotes: {
-      quest3TierRemote: process.env.VITE_QUEST3TIER_REMOTE_URL ?? "http://localhost:5174/assets/remoteEntry.js",
-      quest5TierRemote: process.env.VITE_QUEST5TIER_REMOTE_URL ?? "http://localhost:5175/assets/remoteEntry.js",
-      quest5TierEgRemote: process.env.VITE_QUEST5TIEREG_REMOTE_URL ?? "http://localhost:5176/assets/remoteEntry.js",
+      quest3TierRemote: requireEnv("VITE_QUEST3TIER_REMOTE_URL", "http://localhost:5174/assets/remoteEntry.js"),
+      quest5TierRemote: requireEnv("VITE_QUEST5TIER_REMOTE_URL", "http://localhost:5175/assets/remoteEntry.js"),
+      quest5TierEgRemote: requireEnv("VITE_QUEST5TIEREG_REMOTE_URL", "http://localhost:5176/assets/remoteEntry.js"),
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
remotes: {
quest3TierRemote: process.env.VITE_QUEST3TIER_REMOTE_URL ?? "http://localhost:5174/assets/remoteEntry.js",
quest5TierRemote: process.env.VITE_QUEST5TIER_REMOTE_URL ?? "http://localhost:5175/assets/remoteEntry.js",
quest5TierEgRemote: process.env.VITE_QUEST5TIEREG_REMOTE_URL ?? "http://localhost:5176/assets/remoteEntry.js",
},
remotes: {
quest3TierRemote: requireEnv("VITE_QUEST3TIER_REMOTE_URL", "http://localhost:5174/assets/remoteEntry.js"),
quest5TierRemote: requireEnv("VITE_QUEST5TIER_REMOTE_URL", "http://localhost:5175/assets/remoteEntry.js"),
quest5TierEgRemote: requireEnv("VITE_QUEST5TIEREG_REMOTE_URL", "http://localhost:5176/assets/remoteEntry.js"),
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/vite.config.ts` around lines 14 - 18, The remote URL defaults in the
defineConfig callback are too permissive because they fall back to localhost
during production builds as well. Update the Vite config logic around the
remotes object to use the callback’s command/mode context and only allow
localhost fallbacks in development; for production build paths, require the
VITE_QUEST3TIER_REMOTE_URL, VITE_QUEST5TIER_REMOTE_URL, and
VITE_QUEST5TIEREG_REMOTE_URL env values and fail loudly if any are missing. Use
the existing remote keys quest3TierRemote, quest5TierRemote, and
quest5TierEgRemote to locate the change.

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.

2 participants