Micro frontends promise independent teams and independent deploys, but without a CDN-aware delivery strategy, Module Federation becomes a runtime liability. The CDN is not just a cache — it is the integration layer that makes federated modules fast, version-safe, and resilient.
When every team ships its own JavaScript bundle to production on its own schedule, the browser becomes the integrator. Webpack 5 Module Federation makes this possible by letting a host application consume remotes at runtime. The CDN determines whether that runtime integration is instant or painfully slow, consistent or broken. This guide covers the architecture, caching, and deployment patterns that make CDN-backed micro frontends work at scale.
Why Micro Frontends Need a Different CDN Strategy
Traditional single-page applications have one build, one deployment, and one cache invalidation. Micro frontends invert that model. A single page may compose five remotes owned by three teams, each deployed dozens of times per week. If you treat them like a monolith, you get version skew, long cache tails, and cascading failures.
A federated architecture introduces two distinct artifact types with opposite caching requirements:
- Immutable chunks — content-hashed JS/CSS files (e.g.,
checkout.8f3a1b.js) that never change. Cache forever at the edge. - Mutable manifests —
remoteEntry.jsand federation manifests that map a remote name to its latest chunks. These must be fresh, but also highly available.
Your CDN must serve both correctly. Understanding your CDN architecture overview is critical before you split the frontend, because origin topology, cache key design, and purge strategy directly affect federation reliability.
How Module Federation Works at the Edge
With Module Federation, the host does not bundle remote code at build time. At runtime it fetches remoteEntry.js for each remote, which registers exposed modules on window and loads the required chunks on demand.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
// webpack.config.js — Host new ModuleFederationPlugin({ name: "host", remotes: { header: "header@https://cdn.flyfishnwa.com/mfe/header/remoteEntry.js", checkout: "checkout@https://cdn.flyfishnwa.com/mfe/checkout/remoteEntry.js", search: "search@https://cdn.flyfishnwa.com/mfe/search/remoteEntry.js" }, shared: { react: { singleton: true, requiredVersion: "^18.0.0" } } }); // webpack.config.js — Remote (team-checkout) new ModuleFederationPlugin({ name: "checkout", filename: "remoteEntry.js", exposes: { "./Cart": "./src/Cart", "./CheckoutFlow": "./src/CheckoutFlow" }, shared: { react: { singleton: true } } });
The URLs in remotes are CDN URLs, not origins. That is intentional. The browser should never contact your S3 bucket or Kubernetes ingress directly. All federation traffic flows through the edge, where you can apply optimized DNS resolution, TLS, compression, and failover.
Independent Deployment Without Breaking the Host
Independent deployment is the goal, but naive implementation breaks users mid-session. If team Checkout deploys a new remoteEntry.js that points to chunks that are not yet replicated to all edge PoPs, some users fetch a manifest that references 404s.
The solution is an atomic, versioned deployment pipeline:
- Upload immutable chunks first to object storage with
Cache-Control: public, max-age=31536000, immutable. Wait for replication. - Upload new
remoteEntry.jsto a versioned path like/mfe/checkout/v2.4.1/remoteEntry.jsand also to/mfe/checkout/remoteEntry.js(the live pointer). - Purge the CDN cache for the live pointer only using surrogate keys (e.g.,
purge key=mfe-checkout) — not a wildcard purge. - Keep previous versions addressable for instant rollback and for users with long-lived sessions.
For the host, avoid hardcoding remote URLs at build time. Inject them at the edge via an edge computing worker or via a lightweight manifest JSON that the host fetches on boot:
// https://cdn.flyfishnwa.com/mfe/manifest.json — generated by CI, served at edge { "header": "https://cdn.flyfishnwa.com/mfe/header/v3.2.0/remoteEntry.js", "checkout": "https://cdn.flyfishnwa.com/mfe/checkout/v2.4.1/remoteEntry.js", "search": "https://cdn.flyfishnwa.com/mfe/search/v1.9.8/remoteEntry.js" } // host runtime — dynamic remote resolution const manifest = await fetch("/mfe/manifest.json").then(r => r.json()); await __webpack_init_sharing__("default"); await loadRemote(manifest.checkout); // uses Module Federation runtime API
This decouples the host deployment from remote deployments. The host can ship without rebuilding when a remote updates, and you can perform canary releases by having the edge worker return different manifests based on a cookie or percentage.
Caching Strategy: The Table That Matters
Getting cache headers wrong is the most common failure mode. Use this reference for every micro frontend artifact:
| Artifact | Cache-Control | CDN TTL | Purge Strategy |
|---|---|---|---|
Content-hashed chunks*.<hash>.js / .css |
public, max-age=31536000, immutable |
1 year (edge + browser) | Never purge. New deploy = new files. |
| remoteEntry.js (live pointer) | public, max-age=60, s-maxage=120, stale-while-revalidate=600 |
60–120s | Surrogate key purge on deploy |
Versioned remoteEntry.js/v2.4.1/remoteEntry.js |
public, max-age=31536000, immutable |
1 year | Never purge. Immutable. |
| manifest.json | public, max-age=30, s-maxage=60 |
30–60s | Purge on any remote deploy |
| Host index.html | no-cache, must-revalidate |
Bypass or 0s | Purge on host deploy only |
Note the use of stale-while-revalidate for remoteEntry.js. This keeps the edge fast during a purge — users get the stale manifest instantly while the edge revalidates in the background, avoiding a thundering herd to origin.
Pair this with image optimization at the edge for media-heavy micro frontends; each remote should not reimplement responsive images when the CDN can handle format negotiation and resizing.
Performance: Eliminating the Federation Waterfall
Module Federation adds network requests to the critical path. Without optimization, the sequence is: host HTML → host JS → fetch remoteEntry.js → fetch remote chunk → render. That is three round trips before pixels appear.
Mitigations that belong at the CDN layer:
- Preload hints from the edge. Have the edge worker inject
<link rel="preload">or103 Early Hintsfor known remotes based on the route. For/checkout, preloadcheckout/remoteEntry.jsbefore the host JS even parses. - HTTP/2/3 push or Early Hints. Modern CDNs can send Early Hints for federation chunks discovered via the manifest, cutting 100–300ms on slow networks.
- Shared dependencies as separate CDN assets. Mark
react,react-dom, and design system packages as singletons and serve them from/shared/with long TTLs. They are downloaded once and reused across remotes. - Edge-side composition (optional). For SEO-critical pages, compose remotes at the edge into a single HTML stream using ESI or Workers, so the browser receives fully rendered markup. This also improves Core Web Vitals by reducing CLS from late-loading remotes.
Measure with Real User Monitoring segmented by remote. If search remote has a 90th percentile load of 800ms while others are 200ms, the problem is likely its chunk size or cache hit ratio, not the host.
Resilience, Security, and Rollback
A federated frontend is only as reliable as its weakest remote. The CDN must provide isolation:
- Fail-open strategy: Wrap remote imports in error boundaries and have the CDN serve a fallback
remoteEntry.jsthat exposes a degraded component. Use edge health checks to automatically serve the last known good version if origin is 5xx. - Subresource Integrity (SRI): Generate integrity hashes at build time and validate them in the host. The CDN should not strip them.
- CORS and CSP: Serve all remotes from the same CDN hostname to avoid CORS preflights. Set a strict
Content-Security-Policythat allowsscript-src 'self' https://cdn.flyfishnwa.com. - DDoS and origin protection: Each remote origin is a potential attack surface. Put all origins behind the CDN and enforce DDoS protection and origin shielding so a flood to one micro frontend does not take down the host.
# CloudFront / Fastly / Cloudflare — example cache behavior location ~ ^/mfe/(?<mfe>[^/]+)/remoteEntry\.js$ { proxy_pass https://origin-$mfe.s3.amazonaws.com; add_header Cache-Control "public, max-age=60, s-maxage=120, stale-while-revalidate=600"; add_header Surrogate-Key "mfe-$mfe"; add_header Vary "Accept-Encoding"; } # Immutable chunks — never revalidate location ~ \.[a-f0-9]{8}\.(js|css)$ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; }
Checklist for Production
Before you go live with CDN-delivered Module Federation, verify these:
- All remotes use content hashing and immutable caching; no overwrites.
remoteEntry.jsandmanifest.jsonhave short TTLs + surrogate keys and are purged atomically in CI.- Host resolves remotes dynamically via edge manifest, not build-time URLs.
- Shared singletons are deduplicated and cached separately.
- Error boundaries and fallback remotes are tested with CDN failover.
- Early Hints / preload headers are emitted by the edge for critical routes.
- Monitoring tracks cache hit ratio, remote load time, and version skew per PoP.
Done right, the CDN turns Module Federation from a clever Webpack trick into a true independent deployment platform. Teams ship when ready, users get instant updates without hard refreshes, and the edge guarantees consistency and speed.
Frequently Asked Questions
How does a CDN handle Module Federation remoteEntry.js files?
The CDN serves remoteEntry.js with a short TTL (60–300 seconds) and stale-while-revalidate. Deploy it immutably under a versioned path and keep a live pointer that you purge via surrogate keys. This gives you instant updates without long cache tails and allows instant rollback to a previous versioned file.
Should micro frontends share a single CDN distribution or use separate ones?
Use a single distribution with path-based origins (e.g., /mfe/header/*, /mfe/checkout/*). This simplifies edge logic, TLS, and WAF policies, and lets an edge worker compose a unified manifest. Separate distributions only make sense when teams need isolated security, billing, or geographic controls.
How do you avoid cache poisoning when independently deploying micro frontends?
Never overwrite content-hashed assets. Each build produces new filenames. Only remoteEntry.js and the version manifest are mutable, and they are protected by short cache, surrogate-key purges, and an upload order that ensures chunks exist at the edge before the manifest points to them.
Can Module Federation work with edge computing for personalization?
Yes. An edge worker can inspect cookies, headers, or geo data and return a tailored federation manifest — for example, serving an experimental checkout remote to 10% of users. This moves personalization to the edge with zero client-side waterfall and no host redeploy.