Micro-frontends & Monorepo

The interview trap here is enthusiasm. Micro-frontends are an organizational solution, not a technical one — they buy team autonomy and you pay in bundle duplication, version skew, and ops complexity. The Lead move is to know when NOT to, and to reach for a monorepo first.

1The reframe — MFEs solve an org problem, not a code problem

Micro-frontends are “an architectural style where independently deliverable frontend applications are composed into a greater whole.” [Fowler] But the reason to adopt them is almost never technical. The honest framing: you split the frontend so you can split the teams — independent deploys, independent stacks, end-to-end ownership. If you don't have a team-scaling problem, you don't have a micro-frontend problem.

The line that earns the role: “Micro-frontends are an organizational solution, not a technical one. If the whole app is built by five people, a monolith is 10× more efficient — you only reach for MFEs when you have so many teams that coordinating one deploy pipeline has become the bottleneck.” [Bitovi] Lead with the cost, not the hype.

The three benefits Fowler names — and they're all about people, not performance: [Fowler]

One-liner

“Micro-frontends decouple teams, not code. I adopt them when org coordination is the bottleneck — not to make the app faster. They make it slower.”

2How they're stitched together — the integration spectrum

“Micro-frontend” is a goal; the interesting question is where the composition happens. The core picture: three independently-built, independently-deployed apps (each its own team, repo, pipeline) are stitched into one page the user sees as a single product. Watch the shell load each slice on its own:

Fowler lists five approaches; know them as a spectrum from build-time (coupled) to runtime (decoupled): [Fowler]

ApproachHowTrade-off
Build-time (npm packages)each MFE is a published package the shell importsSimple, but re-couples deploys — shell must rebuild/redeploy to ship any change. Defeats the purpose.
Server-side compositionedge/SSR assembles fragments (SSI, Tailor)Great for SEO/first paint; harder for rich client interactivity.
Iframeseach MFE in its own iframeBulletproof isolation (CSS/JS) but awful for routing, deep-linking, shared state, height.
Run-time via JavaScriptshell fetches & mounts remote bundles at runtimeMost flexible & common. single-spa orchestrates; Module Federation shares deps.
Web Components / import mapsstandards-based custom elements; import maps map bare specifiers to URLsFramework-neutral, no bundler lock-in; less tooling/DX than Module Federation.

Module Federation (Webpack 5 / now Rspack & the standalone @module-federation/* runtime) is the de-facto runtime tool: a host loads code from a remote at runtime and they can share dependencies (one React instance instead of three). single-spa is the orchestrator/router; import maps are the native-browser primitive. Know all three names. [module-federation.io]

// host webpack.config — load "cart" remote at runtime, share ONE React
new ModuleFederationPlugin({
  name: 'shell',
  remotes: { cart: 'cart@https://cart.example.com/remoteEntry.js' },
  shared: {
    react:      { singleton: true, requiredVersion: '^18.2.0' },
    'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
  },
});
// singleton:true ⇒ load ONE shared copy. The version negotiation here
// is exactly where "version skew" lives (see §3).
One-liner

“Pick by where composition happens. Build-time re-couples your deploys; iframes over-isolate; runtime JS — Module Federation or single-spa — is the sweet spot, and it's where the hard problems live.”

3The costs — what you actually pay (this is the graded part)

Anyone can recite the benefits. A Lead names the bill. Five real costs, each with the one-liner you'd say in the room:

Bundle duplication

the performance tax

“Independently-built bundles can cause duplication of common dependencies, increasing the bytes users download.” [Fowler] Three MFEs = three lodash/React unless you share — and sharing reintroduces coupling.

Version skew

the autonomy paradox

The instant you force every team onto the exact same version of a shared lib, you've killed independent deploys. Let them drift and singleton:true negotiation can load an incompatible React → runtime crash. [LogRocket]

Lost types & fate sharing

runtime, not compile-time

Remotes load at runtime, so TypeScript types are lost at the boundary — a breaking change in a shared component only blows up after deploy, in the consumer. [AngularArchitects] A bug in one remote can take down the shell (fate sharing).

Shared state & UI cohesion

the integration mess

Passing the auth token / cart between a host and a remote “can quickly spiral into a mess.” [LogRocket] Keeping fonts, spacing, and a11y consistent across independently-shipped teams needs a hard-enforced design system.

Trade-off — the core tension, in Fowler's words: “There is an inherent tension between our desire to let teams compile their applications independently so that they can work autonomously, and our desire to build our applications in such a way that they can share common dependencies.” [Fowler] Every MFE decision is a point on that line. Plus a 5th cost: operational complexity — many repos, pipelines, and runtime-integration infra you need real automation maturity to run without chaos.
One-liner

“The MFE bill: duplicated bytes, version skew, lost types, fate sharing, and ops overhead. I only pay it when team autonomy is worth more than all five — and at most shops, it isn't.”

4Deep dive — two versions of the same library, resolved

This is the favourite follow-up: “Shell needs React 18.2, a remote was built against 18.3 — what actually happens?” Module Federation maintains a shared scope: a runtime registry where every container publishes the versions it brought and consumers resolve the best match. The outcome depends entirely on how you flagged that dependency in shared. [MF docs]

ConfigWhat happens with a version mismatch
default (not singleton)Semver match: if a compatible version exists in the scope, the highest compatible one is shared. If incompatible, each app falls back to its own copytwo versions load side-by-side. Safe, but duplicated bytes. [AngularArchitects]
singleton: trueExactly one copy loads — the highest version known at init, regardless of compatibility. If versions are incompatible you still get one copy + a console warning, and the lower-version consumer runs against the higher React (may break at runtime). Required for React/React-DOM — two copies = broken hooks/context.
strictVersion: trueTurns that warning into a hard runtime error — fail fast instead of silently running on an incompatible singleton.
requiredVersion: '>=18 <20'Widens the accepted range so a higher major (e.g. 19) is treated as compatible instead of triggering a fallback/duplicate. Your lever to permit drift deliberately.
eager: truePuts the shared dep in the initial chunk (synchronous) instead of an async negotiated load — sometimes needed for the host, but it bloats the entry bundle. Prefer lazy.
// The mental model: singleton = "one copy, highest wins, warn on mismatch".
shared: {
  react: { singleton: true, strictVersion: true, requiredVersion: '^18.2.0' },
}
// → exactly one React. If a remote shows up with an incompatible 17.x,
//   strictVersion makes it CRASH LOUDLY at load, not corrupt hooks silently.
The trap to name: there is no magic that makes two incompatible majors coexist and stay deduplicated. You pick your poison: duplicate (default fallback → bytes + possible double-context bugs for stateful libs) or one shared copy (singleton → skew risk, or a crash with strictVersion). That's why shared deps are governed, not negotiated: a single source of truth for React/design-system versions (renovate, a shared config) so the runtime never has to guess. For stateful singletons (React, the router, a shared store) you must dedupe; for stateless utilities (lodash, date-fns) duplication is merely a size cost.

Worked examples — predict the resolution, then reveal

The drill: read the config line, say the outcome out loud (how many copies? whose version wins? any warning/crash?), then click to check. Assume each party's requiredVersion is the default caret ^ of the version it declares (so same major = compatible) unless noted. Left border: clean · duplication / warning · crash.

Scope provided: {17.0, 18.0, 18.2}. shell needs ^17 → no 18.x fits → falls back to its own 17.0. r1 (^18.0) and r2 (^18.2) both take the highest 18.x → 18.2. ⚠️ Two copies: 17.0 (shell alone) + 18.2 (r1, r2). No crash. A party on a different major just gets its own copy — graceful, but duplicated bytes.
Exact pins, not carets. shell & r1 accept only 18.2.0 → share 18.2.0. r2 accepts only 18.3.0 → uses 18.3.0. ⚠️ Two copies (18.2.0 + 18.3.0) — even though all are React 18. Over-strict requiredVersion pins defeat sharing within the same major. Use caret ranges, not exact pins.
One copy allowed; highest provided = 18.5; everyone forced onto it. 18.5 satisfies every ^18.x range. ✅ One shared copy: 18.5. No warning. Within a major, singleton silently picks the highest minor — the common happy path.
One copy; highest provided = 19.0; r1 forced onto it. r1 needs ^18 → 19.0 doesn't satisfy → warning; r1 renders against React 19. ⚠️ One copy: 19.0 + a console warning for r1 (skew risk). The host being on a higher major drags every remote up — and warns the ones expecting the lower major.
Highest provided = 19.0 forced. shell needs ^18 → unsatisfied. Without strictVersion this is a warning; with it, the unsatisfied singleton throws. ❌ Hard runtime error at load → white screen. strictVersion trades a silent-skew warning for a loud crash — fail fast instead of running on the wrong major.
Highest provided = 19.0 forced. shell widened its range to accept up to <20, so 19.0 satisfies shell too → no mismatch. ✅ One shared copy: 19.0. No warning. Widening requiredVersion is how you consent to a higher major and silence the skew warning deliberately.
React is consumed at first render → the 18.0 singleton is already live (highest known at init was just shell's 18.0). The click loads r1, which needs >=18.9; but a singleton can't be swapped once live → r1 gets the existing 18.0, which is below its floor → warning; r1 can't upgrade it. ⚠️ r1 forced onto the already-live 18.0 (below what it required) + warning. Timing: a late remote resolves against the already-instantiated singleton — even a higher minor can't upgrade it, and the latecomer can be pushed below its own required version.
Initially shell + r1 share the highest 18.x = 18.4 (one copy). The click loads r2 needing ^20; nothing in scope satisfies it → r2 loads its own React 20 on demand. ⚠️ Two copies, the second paid lazily on click: 18.4 + 20.0. No crash. Non-singleton late remotes just bring their own copy — graceful, but the duplication arrives at click time, not page load.
One-liner

“Default federation loads the highest compatible version and falls back to a private copy when they clash. singleton:true forces one copy — highest wins, warning on mismatch — and strictVersion turns that warning into a crash. React must be a singleton; for the rest I govern versions so the runtime never guesses.”

5How micro-frontends talk to each other

The golden rule mirrors microservices: prefer the loosest coupling that does the job. The more two MFEs know about each other, the less independent they actually are. The hierarchy, loosest-to-tightest: [Thoughtworks]

URL / router

loosest — prefer for navigational state

City, dates, filters in the URL (Lesson 13). Any MFE can read it; nothing imports anything. Shareable + back-button-safe. The most decoupled “shared state” there is.

Props & callbacks (host → remote)

explicit contract, parent→child

Host owns state, passes data down and callbacks up (e.g. catalog gets an onAddToCart). Typed, traceable, easy to test. Best default for host-orchestrated composition.

Custom events / pub-sub

decoupled sibling↔sibling

Native window.dispatchEvent(new CustomEvent('cart:add',{detail})) + listeners, or a tiny event bus. MFEs never import each other. Trade-off: implicit, untyped contract — can rot into spaghetti without discipline.

Shared state store

tightest — use last

One federated singleton store (Zustand/Redux) all MFEs read/dispatch to. Structured, but couples everyone to a shared schema — the “shared-state mess” from §3. Reserve for genuinely global state (auth/cart).

// Decoupled sibling→sibling: search MFE fires, cart MFE listens. No import.
window.dispatchEvent(new CustomEvent('app:add-to-trip', { detail: { hotelId } }));
// cart MFE, mounted independently:
window.addEventListener('app:add-to-trip', e => addHotel(e.detail.hotelId));
Trade-off — implicit contracts are the real risk. Events/shared-stores have no compile-time contract; a producer can change detail's shape and silently break every consumer (the lost-types problem again). The fix: a shared, versioned contracts package (event names + payload types) in the monorepo, imported by both sides — so even an event bus is type-checked. And never reach into another MFE's internal DOM/state; only talk through the published contract.
One-liner

“I communicate by the loosest channel that works: URL for navigational state, props/callbacks for host-to-child, custom events for decoupled siblings, a shared store only for truly global state like auth. And I type every event with a shared contracts package — an untyped event bus is just distributed spaghetti.”

6Wiring, deploying & caching remotes — the operational reality

First, two words people (and I, earlier) mix up

Get these straight and the rest is obvious:

They're not the same thing — so I'll stop saying "manifest" loosely and just say entry file. The mental model that makes everything click: one small mutable pointer at a stable URL, pointing at many large immutable chunks whose names change every build.

Static vs dynamic remotes (with the runtime API)

Static (build-time)

URL baked into the host bundle

The URL is a constant in the host's config, compiled in. Simple, but to change a URL, support a new env, or roll a remote back you must rebuild & redeploy the host.

Dynamic (runtime)

URL fetched at runtime

The host reads remote URLs from runtime config and loads modules on demand with loadRemote. Per-env URLs and version swaps with no host rebuild — and it's what makes rollback a config flip.

// STATIC — URL is a compile-time constant in the host's build config:
remotes: { cart: 'cart@https://cart.example.com/remoteEntry.js' }

// DYNAMIC — URL comes from runtime config, loaded on demand:
import { init, loadRemote } from '@module-federation/enhanced/runtime';

const cfg = await fetch('/config.json').then(r => r.json()); // {cartUrl: '…/v1.2.3/remoteEntry.js'}
init({
  name: 'shell',
  remotes: [{ name: 'cart', entry: cfg.cartUrl }],   // ← decided at RUNTIME, not build
});
const { addToCart } = await loadRemote('cart/api');  // lazy-load an exposed module

The paradox you spotted — “if it's a fixed URL and it's cached, how does it ever update?”

Exactly the right question. With a static remote the host always fetches the same URL: …/remoteEntry.js. When the remote team redeploys, they overwrite that file in place — same URL, new contents that now point at the new hashed chunks. So:

So the resolution is a deliberate split: the entry file is the one thing you must serve un-cached (it's tiny — revalidating it on every load is cheap), while the heavy chunks it points to are cached forever, safely, because their filenames change when their content changes. Play it through both ways:

1 · Hostfetches the
entry file
2 · remoteEntry.jspoints to
cart.8f3a9c.js
3 · CDN chunksavailable:
cart.8f3a9c.js
Click a scenario to watch what the host resolves.

After a deploy the CDN now serves cart.b2d4e1.js (new hash) and the old chunk is purged. The only difference between a clean update and a white screen is whether the host got the fresh entry pointer or a cached one.

Solutions — defense in depth

# The one cache rule that prevents the outage:
remoteEntry.js   →  Cache-Control: no-cache                  # tiny pointer — always revalidate
cart.8f3a9c.js   →  Cache-Control: immutable, max-age=31536000  # hashed → cache forever
“But what if the entry file itself gets cached?” Then you're frozen on the old version until the cache expires (or broken, if the old chunks were purged). The uncomfortable truth: there is always exactly one “pointer” file that must not be cached — you can't escape it, you can only make it as small as possible. The versioned approach does exactly that: the host points at a one-line registry like { "cart": "v1.2.3" } served no-cache; every remoteEntry.js then lives at an immutable versioned URL and is cached forever. You've shrunk the uncacheable surface from "the whole entry file, every remote" down to "one tiny JSON line." Rollback = change that line to v1.2.2.

“If static + no-cache already updates correctly, why do we need dynamic?”

Because they fix different axes. no-cache answers “give me the freshest contents of a URL I already know.” Dynamic answers which URL, which version, and whether to load at all — decided at runtime.” Static + no-cache can't touch that second axis: it only ever gives you always-latest, and “always-latest with no way to pin or roll back” is a liability on a revenue funnel.

You need…Static + no-cacheDynamic
Latest content of a known remote✅ yes✅ yes
Pin a version / roll back with no host redeploy❌ version is compiled in✅ flip a registry line
Different URLs per env / region / tenant❌ N host builds✅ one artifact, runtime URL
Add / remove a remote without touching the host❌ edit + redeploy host✅ edit the registry
Conditional / lazy / A-B load (flag, route, locale)❌ fixed compiled list✅ decide at runtime
Atomic, race-free rollout❌ overwrite-in-place window✅ publish vN, then flip the pointer

So static + no-cache is perfectly fine for a small, fixed set of always-latest remotes. The moment you need version control, rollback, per-env URLs, or conditional loading, that's the job dynamic does — and no-cache was never the tool for any of it.

Where the version lives — the config.json registry

In the dynamic snippet the host fetched /config.json. That's the host-level registry: a tiny JSON mapping each logical remote → the exact versioned entry URL to load right now. Editing it is how you release or roll back.

// config.json — the host's version registry (a "pointer to pointers")
{
  "cart":    "https://cart.example.com/v1.2.3/remoteEntry.js",
  "search":  "https://search.example.com/v4.0.1/remoteEntry.js",
  "account": "https://account.example.com/v2.7.0/remoteEntry.js"
}
// release  = bump "v1.2.3" → "v1.2.4";  rollback = set it back to "v1.2.2"

Don't mix the three “pointer-ish” files — they sit at two levels:

Where it lives: owned by the platform / host layer (not any single remote team — it's what decides which versions compose together). Common homes: a static file on the shell's own CDN, an edge KV store (Cloudflare KV, etc.) for instant global updates, or a small config / feature-flag service so version selection can even be per-user / per-region / A-B. The platform pipeline writes to it.

Is it cached? No — this is THE one no-cache file. Everything below it is immutable: each remoteEntry.js now sits at a versioned URL (contents never change) and chunks are content-hashed. So you've moved the uncacheable surface from “every remote's entry, overwritten in place” down to one tiny JSON line read at startup. Two caveats to name: it's on the critical path (the host can't load any remote until it resolves — keep it tiny, edge-cache it, or inline it into the initial HTML to skip the round-trip), and it's now a single source of truth — needs a last-known-good fallback and tight access control, because one edited line repoints production traffic.
One-liner

remoteEntry.js is a pointer at a stable URL — it's the one file I never cache, while the content-hashed chunks I cache forever. If I cache the pointer, the host is frozen on the old build — or white-screens with a ChunkLoadError if the old chunks were purged. So: entry no-cache, chunks immutable, keep old chunks through the rollout, and drive versions from a tiny config.json registry so rollback is a one-line config flip — never a host redeploy. Static + no-cache only ever gives me ‘always latest’; the registry gives me version control.”

7“When would you NOT use micro-frontends?” — the question they'll ask

It's in the bank verbatim, so have the answer ready. Say no to MFEs when:

And the pivot that scores points: “before micro-frontends, I'd reach for a monorepo” — you get shared code, atomic cross-cutting changes, and team boundaries without the runtime integration tax. That's §5.

8The often-better answer — monorepo (Nx / Turborepo)

A monorepo is many projects in one repo with shared tooling; a polyrepo is one repo per project. Crucial distinction the panel wants: monorepo is a build/source-organization choice; micro-frontend is a runtime-composition choice. They're orthogonal — you can have a monolith in a monorepo, or MFEs across polyrepos. Conflating them is a common junior tell.

MonorepoPolyrepo
Code sharingTrivial — import a local packageHard — publish/version npm packages
Cross-cutting changeAtomic — one PR spans app + libMulti-repo dance, version coordination
Team independenceBoundaries enforced by toolingHard repo boundaries by default
CI costNeeds affected/caching or it explodesNaturally scoped per repo

The reason monorepos scale is the project graph + “affected” builds + caching. Nx/Turborepo diff Git, walk the dependency graph to find only the projects touched (and their dependents), and rebuild/test only those — cutting CI by up to 90%. Task inputs are hashed (source + config + env) into a cache key; identical inputs replay a cached result, and remote cache shares those hits across the whole team/CI. [Nx]

Turborepo

simple, fast, JS-focused

Caching + task pipelines with minimal config (turbo.json). Rewritten Go→Rust for speed. Lighter: no code-gen, no graph viz, no boundary lint. Great default for a JS/TS app.

Nx

full platform, big orgs

Project-graph viz, generators, module-boundary enforcement (lint rule: this lib may not import that one), distributed task execution, remote cache. Steeper curve; pays off at scale. [Nx]

That module-boundary enforcement is the killer Lead point: in a monorepo you can codify architecture as a lint rule — tag libs (scope:checkout, type:ui) and fail CI if checkout imports admin, or a UI lib imports a data lib. You get team boundaries at build time, no runtime federation needed. [Nx]

One-liner

“Monorepo and micro-frontend aren't the same axis: one is build-time source layout, the other is runtime composition. I'd get 80% of the ‘independent teams’ win from a monorepo with affected-builds and boundary lint — and only add MFEs when teams truly need independent deploys.”

9“Build a feature that touches multiple domains” — the design question

A classic Lead scenario: “Add a ‘Book with points’ promo that spans search, the property page, the cart, and the user wallet — how do you ship it across micro-frontends owned by four teams?” The senior move is to challenge the premise first, then show the mechanics.

  1. Question the boundaries. A feature that touches every MFE is usually a signal the seams are wrong. Boundaries should follow business domains / vertical slices (Conway's law) so most features live inside one. If many features cut across, you've mis-drawn the seams — that's the finding, not the feature.
  2. Name an owner. Cross-cutting work needs a single orchestrator — usually the host/shell, or a dedicated “experience” team. Diffuse ownership across four teams is how it stalls.
  3. Compose, don't reach in. The host orchestrates; each domain exposes an explicit contract (props it accepts, events it emits). No MFE touches another's internal DOM or store. Types live in a shared contracts package.
  4. Flag-gate the rollout. Remotes deploy independently, so you cannot ship the four changes atomically. Put the whole feature behind a feature flag that lights up only once all four remotes have shipped their piece — otherwise it half-renders in production.
  5. Don't build a distributed transaction in the UI. Coordinating wallet-debit + booking across remotes belongs on the backend (one API/BFF), not orchestrated by browser events across four bundles.
The punchline — this is the strongest monorepo argument. In polyrepo MFEs this feature is a multi-repo, multi-deploy, flag-coordinated dance. In a monorepo it's one atomic PR spanning four domains, with shared types and boundary lint keeping it honest — you still flag the runtime rollout if the apps deploy separately, but the code change is atomic and type-safe. Cross-cutting features are exactly where “monorepo first, MFE only at deploy seams” pays off.
One-liner

“A feature touching every micro-frontend usually means my boundaries are wrong. If it's genuine: the host orchestrates through explicit, typed contracts, the cross-domain logic lives on a BFF not in browser events, and because remotes deploy independently I gate the rollout behind a flag so nothing half-ships.”

10The Platform angle & Lead framing

This is exactly the rare shop where MFEs can be justified — huge org, many product teams (search, property, checkout, account, B2B). But the SEO/performance constraints push back hard: duplicated bundles hurt LCP, and runtime federation complicates SSR/streaming (Lessons 09–10). So the defensible Lead position is nuanced, not binary:

Full loop

Concept: MFEs decouple teams' deploys via runtime composition. Trade-off: bundle duplication, version skew, lost types, fate sharing, ops cost. Anchor: “At platform scale I'd run a monorepo backbone with boundary lint + a shared design system, and carve out MFEs only where a team genuinely needs an independent deploy cadence — SSR-composed, to protect SEO.” Impact: teams move independently without a bundle-size or skew regression on a revenue-critical, SEO-driven funnel. Invite: “Where would you draw the MFE seam for a booking flow — and would you accept the duplicate React?”

11Rapid-fire — other things the panel may probe

Short, confident answers to the adjacent questions that often follow. Each is one trade-off, not an essay.

QuestionThe crisp answer
Routing across MFEs?Shell owns the top-level routes and delegates a path prefix to each remote (which owns its sub-routes). single-spa's “activity functions” decide which app is active for a URL. One router of record; no two MFEs claiming the same path.
CSS isolation?Don't rely on global CSS. Use CSS Modules / scoped or hashed class names, Shadow DOM (web-component MFEs), and a shared design-token layer for consistency. Global resets are the #1 cross-MFE style bug.
Error isolation / one remote crashes?Wrap each remote in an error boundary so a crash degrades to a fallback instead of white-screening the shell — the direct mitigation for fate sharing. Same for a remote that fails to load: retry + fallback UI.
Sharing auth/session?Host owns auth; pass the token down (props/context) or via a single shared auth store. Remotes must not each re-implement login or store their own token — one source of truth (ties Lesson 18).
SSR with Module Federation?Harder — federation is client-runtime-first. Options: server-side composition of fragments, or SSR each MFE and stitch at the edge. This is exactly why SEO-critical apps lean server-side, not client federation.
Versioning / rollback a remote?Ship a versioned remoteEntry (immutable URL per build); the host pins or points at a channel. Roll back by repointing — no shell redeploy. Canary + flags for risky changes.
Testing across MFEs?Each MFE: unit/integration in isolation. The seams: contract tests on the published events/props, plus a thin e2e on the composed shell (Lesson 21). You can't rely on one giant integration suite.
Preventing two Reacts?singleton:true on react + react-dom (§4). Two copies = broken hooks/context. For utilities, duplication is just a size cost, not a correctness bug.

12Check yourself — scenario quiz

1. What's the single best one-line frame for micro-frontends?

2. Which integration approach re-couples your deploys and largely defeats the purpose?

3. In Module Federation, what is shared: { react: { singleton: true } } doing — and what risk does it introduce?

4. Why is a breaking change in a shared federated component especially dangerous?

5. Name three real costs of micro-frontends you'd raise before adopting them.

6. When would you NOT use micro-frontends?

7. A teammate says “let's go micro-frontends so we can share code between teams.” Best Lead pushback?

8. Monorepo vs micro-frontend — the precise distinction?

9. What makes Nx/Turborepo monorepos scale instead of melting CI?

10. What's Nx's module-boundary enforcement and why does a Lead care?

11. For the platform specifically (SEO-critical, high-traffic, many teams), what's the defensible architecture?

12. Shell needs React ^18.2, a remote was built with 18.3, both marked singleton:true. What happens?

13. Without singleton, a host and remote need incompatible major versions of a lib. Default behavior?

14. You want a singleton React mismatch to fail loudly at load instead of silently running on the wrong version. Which flag?

15. Two sibling MFEs must communicate. What's the Lead's default, and the key safeguard?

16. Asked to ship a feature that touches four MFEs owned by four teams. Strongest opening move?

17. A remote deploys a new version. The host's remoteEntry.js was cached by the CDN, and the deploy purged the old chunks. What do returning users see?

18. What's the correct caching setup to make MFE deploys seamless?

19. How do you change a remote's version (or roll it back) without redeploying the host?

20. If serving remoteEntry.js with no-cache already delivers updates, why use dynamic remotes at all?

21. What is the config.json a dynamic host fetches, and should it be cached?

0 / 21 answered

Try this aloud before next session: “A VP wants micro-frontends ‘to move faster.’ In 90 seconds, decide for/against for a booking funnel: name the org signal that justifies it, the three costs you'd accept, why you'd reach for a monorepo first, and where (if anywhere) you'd draw the MFE seam.” Defend it against “but Netflix/Spotify do it.”
Good follow-up topics:
“Quiz me out loud, harder” “single-spa vs Module Federation?” “How do import maps actually work?” “MFE + SSR/streaming — how?” “Static vs dynamic remotes — show config?” “remoteEntry cache headers — exact setup?” “How do MFEs share auth/state safely?”