주요 콘텐츠로 건너뛰기

Micro-Frontends: Are They Still Worth It in 2025?

· 15분 읽기
Evan Carter
Evan Carter
Senior frontend

TLDR:

Micro-Frontend Architecture

Are micro-frontends still worth the complexity in 2025? This guide breaks down the main approaches—Module Federation, single-spa, qiankun, iframes, web components, and import maps—then shows how Feature-Sliced Design helps teams keep boundaries, public APIs, and performance under control as systems scale.

Micro-frontend architecture promised independent teams and independent deployments; in 2025 the real question is whether that promise reduces your total cost of change without breaking UX, performance, or governance. Feature-Sliced Design (FSD) from feature-sliced.design often delivers the same scalability benefits inside a single codebase, and it also makes “extracting a micro-frontend later” far safer.


Table of contents


What a micro-frontend really is (and what it isn’t)

A key principle in software engineering is that architecture is the set of decisions that are expensive to change. Micro-frontends (MFEs) are such a decision: you’re choosing distributed ownership and independent delivery over the simplicity of “one deployable UI.”

In practical terms, a micro-frontend is:

  • A product/domain slice of the UI (often a route subtree or a large widget) that can be built and released with meaningful independence.
  • Integrated into a shell (host/container/root) via runtime composition or orchestration.
  • Governed by explicit contracts: public API surfaces, routing boundaries, events, design tokens, and compatibility rules.

Single-spa describes MFEs as “sections of your UI” that can be managed by different teams and may use different frameworks.

What micro-frontends solve (when they work)

MFEs shine when the bottleneck is coordination (a common symptom in “frontend microservices” organizations):

  1. Team autonomy at scale: fewer merge conflicts, fewer “release trains,” clearer ownership.
  2. Independent deployment: a domain team can ship without waiting for unrelated changes.
  3. Gradual modernization: run legacy and new stacks side-by-side during migration (a core single-spa use case).

What micro-frontends do not automatically solve

MFEs don’t magically fix:

  • spaghetti code inside each micro-app
  • inconsistent patterns across teams
  • coupling through shared dependencies and global state
  • UX drift (layout, accessibility, design system inconsistency)

Without internal structure, you can end up with “many mini-monoliths.”


Common implementation approaches in 2025

Micro-frontends are less about “React vs Vue” and more about how you compose.

1) Module Federation (runtime module sharing)

Webpack 5’s ModuleFederationPlugin lets independent builds provide or consume modules at runtime. The host loads a remote manifest (often remoteEntry.js) and imports exposed modules (routes/components) on demand.

In 2025, “Module Federation” is also bigger than webpack: there are community docs and Vite-oriented plugins that aim to produce and consume federation-compatible artifacts. This matters if you’re standardizing on Vite for DX but still need federated deployment boundaries.

Pseudo-config (simplified):

// host/webpack.config.js
new ModuleFederationPlugin({
remotes: {
checkout: "checkout@https://cdn.example.com/checkout/remoteEntry.js",
},
shared: { react: { singleton: true }, "react-dom": { singleton: true } }
})

// checkout/webpack.config.js
new ModuleFederationPlugin({
name: "checkout",
filename: "remoteEntry.js",
exposes: { "./routes": "./src/routes" },
shared: { react: { singleton: true }, "react-dom": { singleton: true } }
})

Why teams pick it:

  • independent deployment with “native-feeling” integration
  • runtime composition without iframes
  • potential dependency dedupe (when governance is strong)

2) single-spa (application orchestration)

single-spa is a framework for bringing together multiple JavaScript microfrontends in one application. It provides routing-based activation plus mount/unmount lifecycles.

Pseudo-registration:

registerApplication({
name: "@org/checkout",
app: () => System.import("@org/checkout"),
activeWhen: ["/checkout"]
});

3) qiankun (single-spa inspired, enterprise runtime)

qiankun is inspired by and based on single-spa, and emphasizes production features like style isolation and JS sandboxing.

4) Iframes (document-level isolation)

Iframes still matter when you need strong isolation or security boundaries. The sandbox attribute and CSP sandboxing are key tools for restricting embedded content.
The trade-off is heavier UX seams and cross-app communication friction.

5) Web Components (custom elements + Shadow DOM)

Web Components can ship UI as framework-agnostic custom elements; Shadow DOM provides encapsulation against accidental CSS/DOM interference.
They work best for leaf widgets, not whole routed domains.

6) Composable frontend with native ESM + import maps

A “composable frontend” variant uses browser-native modules plus <script type="importmap"> to control specifier resolution.
Import maps have very high global support (Can I use reports ~94.5% usage as of October 2025).

Pseudo-setup:

<script type="importmap">
{ "imports": { "@org/checkout": "https://cdn.example.com/checkout/v42/entry.js" } }
</script>

<script type="module">
const { mountCheckout } = await import("@org/checkout");
mountCheckout(document.querySelector("#slot"), { locale: "en" });
</script>

This reduces bundler lock-in, but you own more of the governance (versions, performance, security).

Comparison table: approaches at a glance

ApproachWhere it shinesTypical trade-offs
Module FederationRuntime composition + shared depsVersion skew, caching, shared library governance
single-spa / qiankunOrchestration + multi-framework migrationExtra runtime layer; still need strict contracts
Iframes / Web Components / import mapsIsolation (iframes), encapsulation (WC), platform-native loading (import maps)UX seams, theming/testing complexity, DIY governance

Frameworks and platforms: Module Federation vs single-spa vs qiankun

Start with: what boundary do you need? The boundary determines the tool.

Boundary types

Runtime module boundary (Module Federation, import maps)
Expose modules; shell imports them at runtime.

Lifecycle/app boundary (single-spa, qiankun)
Expose an application with mount/unmount lifecycles.

Document boundary (iframe)
Strong isolation, heavier integration cost.

Where teams get burned (and how to avoid it)

  1. Implicit coupling through deep imports
    “Just import that internal file” becomes a hidden API. Fix: define public APIs and forbid deep imports.

  2. Version wars in shared deps
    Two React versions or incompatible routers can break runtime invariants. Fix: a “shared kernel” policy (singletons, upgrade cadence, compatibility checks).

  3. The “orchestrator is the architecture” fallacy
    single-spa/qiankun are orchestration, not internal modularity. You still need code organization and boundaries inside each micro-app.

Leading architects suggest choosing the simplest boundary that satisfies your constraints, then investing in contracts and governance.

A pragmatic selection heuristic:

  1. Need hard isolation → iframe.
  2. Need multi-framework migration → single-spa/qiankun.
  3. Need shared runtime + independent deploy → Module Federation.
  4. Want platform-native composition → import maps (with strong governance).

The real challenges: routing, state sharing, and deployments

Micro-frontends succeed when cross-team interfaces are boring.

1) Routing that stays predictable

Treat the URL as a contract.

Recommended model:

  • Shell owns top-level routes.
  • Each MFE owns a route subtree (/checkout/*, /profile/*) and its internal router under that prefix.

Diagram (conceptual): Shell Router → loads Checkout Remote → Checkout Router renders /checkout/*.

Avoid “route weaving” (multiple MFEs must coordinate to render one page). It’s a sign your domain boundaries are wrong.

2) State sharing without recreating a monolith

The fastest way to lose autonomy is a shared global store where everyone writes.

Prefer:

  • Local state inside the owning MFE
  • URL as shared state for filters/tabs/IDs
  • Event-driven integration for cross-domain coordination
  • Backend as source of truth (BFF patterns often reduce frontend coupling)

Pseudo-pattern: typed event bus

export type AppEvent =
| { type: "checkout/finished"; orderId: string }
| { type: "auth/loggedIn"; userId: string };

const handlers = new Set<(e: AppEvent) => void>();

export const emit = (e: AppEvent) => handlers.forEach(h => h(e));
export const on = (h: (e: AppEvent) => void) => (handlers.add(h), () => handlers.delete(h));

A practical rule: share signals, not structures. If two MFEs must “share a data model,” prefer a stable API contract (or events) over a shared in-memory object graph.

State-sharing patternGood forWatch-outs
URL as stateFilters, pagination, selected IDsRequires careful backwards compatibility; avoid leaking internal params
Typed eventsCross-domain coordination (auth, checkout completion)Event taxonomy needs ownership; avoid “god events”
Backend/BFF as source of truthConsistency across MFEs and devicesAdds backend work; requires observability and caching strategy

3) Performance: avoid the “remote entry waterfall”

Micro-frontends can improve performance (independent code splitting), but only if you prevent a runtime waterfall:

  • Shell loads → discovers remotes → fetches remoteEntry → fetches chunks → finally renders.
  • Each extra hop delays LCP and increases failure modes.

Mitigations that work well in 2025:

  1. Route-level prefetching
    Prefetch the remote entry and critical chunks when the user is likely to navigate (hover intent, “next step” in a funnel).

  2. Performance budgets per MFE
    Treat each micro-app like a product: set budgets for bundle size, LCP contribution, and interaction latency (INP).

  3. Share only what must be shared
    A singleton React is great, but over-sharing large libraries can create upgrade lockstep. Aim for a small shared kernel and let everything else duplicate if it’s cheaper.

  4. CSS and design tokens discipline
    Most UX seams come from late-loading styles. Prefer a design system with stable tokens and predictable global styles, and keep “page critical CSS” close to the owning route.

4) Local development and CI/CD: the hidden tax

MFE programs often stall on day-to-day DX:

  • “How do I run Checkout locally against a remote Profile?”
  • “Which remote version is this environment using?”
  • “Why did this build pass but production breaks?”

Practical patterns:

Remote overrides: allow developers to point the shell to localhost for one MFE while consuming others from a dev CDN.
Contract tests: validate the remote public API against the shell during CI (type checks + smoke mount).
Integration environments: a nightly “full mesh” environment catches version skew before users do.

5) Dependency sharing without breaking hooks

With federation-style setups, shared deps are powerful and risky. Keep it stable by:

  1. Declaring a shared kernel (React, router, i18n, design tokens).
  2. Pinning or range-limiting versions with a published policy.
  3. Validating at runtime (shell rejects incompatible remotes).
  4. Enforcing via CI.

6) Independent deployments without production roulette

With N MFEs you run N versions at once, and compatibility combinations grow quickly (roughly N·(N−1)/2).

Practical safeguards:

  1. Contract-first remote entry points (one stable import surface).
  2. Semantic versioning for the public API.
  3. Canary + rollback by switching remote URLs.
  4. Cache discipline (version-pinned remoteEntry, immutable assets).

7) Design system and platform consistency

If teams don’t share a design system and a platform layer (telemetry, i18n, auth), the UI will fragment. FSD is often used as a foundation for controlled reuse and stable layering in such ecosystems.


Decision guide: when micro-frontends are worth it

In 2025, MFEs are worth it when the primary constraint is organizational scale.

Micro-frontends are usually worth it when…

• Many teams ship independently (weekly/daily).
• Domains are loosely coupled (Billing vs Catalog vs Support).
• You can invest in platform ownership (even a small “platform squad”).
• You’re mid-migration and need multiple frameworks to coexist.

Micro-frontends are usually not worth it when…

• You have 1–2 teams and the bottleneck is technical debt inside one repo.
• Most pages require tight cross-feature interaction.
• You can’t standardize UX/platform concerns.

In those cases, improving the monolith’s architecture (boundaries, public APIs, domain slices) usually yields higher ROI.

Micro-frontend vs monolith: measure the cost of boundaries

A useful way to compare a micro-frontend architecture to a modular monolith is to score boundary cost:

  • Change lead time: does splitting reduce PR/release coordination, or does it increase integration work?
  • Rollback safety: can you revert one domain quickly, or do you still need a full “product rollback”?
  • Local development: can a developer run the whole product easily, or do they need a mini-platform just to start?
  • Runtime performance: did you eliminate unused code, or create more network hops and duplicated bundles?
  • Cross-cutting concerns: auth, i18n, analytics—are they consistent, or fragmented across repos?

If “boundary cost” is high, start with a monolith structured by Feature-Sliced Design and revisit MFEs once domains and contracts are clearer.

A lightweight decision matrix

SituationBest starting pointWhy it works
1–2 teams, codebase growingMonolith + FSDKeeps cohesion while enforcing boundaries and predictable structure
Many teams, shared productMFE + platform + FSD inside MFEsPreserves autonomy without sacrificing maintainability
Legacy modernizationsingle-spa/qiankun + FSD adoptionEnables incremental migration with lifecycle orchestration

Feature-Sliced Design as the internal architecture for MFEs

Micro-frontends answer “how do we deploy independently?” Feature-Sliced Design answers “how do we keep code understandable and refactorable for years?”

Feature-Sliced Design Architecture

FSD organizes code by:

  • Layers (responsibility and dependency direction)
  • Slices (business/domain meaning, similar in spirit to DDD bounded contexts)
  • Segments (technical purpose within a slice)

In practice, this pushes teams toward high cohesion within slices and low coupling across them—improving modularity without requiring distributed deployment.

The critical rule: public API gates

FSD’s “public API” is a contract and a gate: consumers import from the slice root (re-exports), not internal files.

Example:

// features/checkout/index.ts (public API)
export { CheckoutPage } from "./ui/CheckoutPage";
export { mountCheckout } from "./entry/mount";
export type { CheckoutMountProps } from "./model/types";

This is exactly the discipline micro-frontends need to avoid hidden coupling.

Comparing internal approaches (and why FSD complements MFEs)

MethodologyWhat it optimizesTypical gap in large products
MVC / MVPTechnical separationBusiness boundaries blur; cross-feature coupling grows
Atomic DesignUI compositionGreat for design systems, weak for domain logic organization
Feature-Sliced DesignDomain-driven modularity + strict dependenciesRequires learning boundaries; needs governance

What FSD looks like inside one micro-frontend

src/
app/
pages/checkout/
widgets/orderSummary/
features/applyCoupon/
entities/order/
shared/ui/
shared/api/

As demonstrated by projects using FSD, a consistent layer/slice model reduces spaghetti code and speeds onboarding through predictability.


Migration playbook: from monolith to micro-frontends with FSD

Micro-frontends are easiest when treated as extraction, not greenfield.

FSD helps extraction by creating clear seams and stable APIs; the docs emphasize incremental migration and practical rules (including merging overly fine slices).

Step 1: Stabilize boundaries in the monolith

  1. Introduce layers/slices and a minimal “map” of domains.
  2. Add public APIs for cross-domain usage.
  3. Enforce import rules via linting/CI.

Step 2: Pick the right first extraction

Good first MFEs are usually:

  • a route subtree with clear ownership (/checkout/*)
  • a high-value domain with its own release cadence
  • a capability you want to modernize independently

Avoid extracting tiny one-page features early.

Step 3: Define a real contract

For each remote:

  • one entry module (mount, routes, or widget)
  • typed input props and emitted events
  • version policy and compatibility checks

Pseudo-contract:

export type CheckoutMountProps = {
userId: string;
locale: string;
onEvent: (e: { type: "checkout/finished"; orderId: string }) => void;
};

export function mountCheckout(el: HTMLElement, props: CheckoutMountProps) {
// render app
}

Step 4: Build the platform “golden path”

Standardize the boring stuff:

  • auth/session integration
  • design system + tokens
  • telemetry/error reporting
  • local dev composition (shell + selected MFEs)

FAQ: micro-frontends in 2025

Are micro-frontends “dead” in 2025?

No. They’re mature. Teams evaluate MFEs on maintainability, performance, and governance—not novelty.

Is Module Federation still a strong default?

Yes for “shared runtime + independent deploy” setups, especially in webpack ecosystems. But it’s not a substitute for contracts and internal architecture.

What’s the safest way to start?

Adopt Feature-Sliced Design first. It improves modularity in a monolith and creates clean seams if you later extract MFEs.

Conclusion

Frontend decoupling is not an optional optimization. It is a fundamental requirement for building flexible, maintainable, and scalable web applications. By separating UI from business logic, adopting event-driven communication where appropriate, abstracting external services, and designing for reuse, teams can dramatically reduce technical debt and increase development velocity.

Adopting a structured architecture like Feature-Sliced Design is a long-term investment in code quality and team productivity. It provides a clear mental model for organizing complexity and evolving systems with confidence.

Ready to build scalable and maintainable frontend projects? Dive into the official Feature-Sliced Design Documentation to get started.

Have questions or want to share your experience? Visit feature-sliced.design to learn more!

Disclaimer: The architectural patterns discussed in this article are based on the Feature-Sliced Design methodology. For detailed implementation guides and the latest updates, please refer to the official documentation.