Edge-Side Includes: Composing Micro-Frontends
How Varnish and Fastly assemble micro-frontend fragments at the CDN edge using edge-side includes, with cache-key isolation and origin failure fallbacks.

In this guide
Table of Contents
Table of contents
Client-side micro-frontend composition — whether via Module Federation, iframes, or a runtime shell fetching remote entries — pushes the assembly cost onto the browser. Every fragment requires a JavaScript execution context, a network round trip past the CDN edge, and a hydration cycle before it becomes visible. For latency-sensitive storefronts and content platforms, that model is frequently the wrong trade-off. Edge-side includes (ESI) move fragment assembly into the CDN’s edge PoP, before the response ever reaches the client, turning composition into an HTTP-layer concern rather than a JavaScript-runtime concern.
This is not a new idea — ESI was standardised by Akamai, Oracle and others back in 2001 — but it has re-emerged as a serious architecture for teams running multiple independently deployed frontend teams against a shared edge cache. The problem it solves is specific: how do you compose a page from fragments owned by different teams, with independent deploy cadences and independent cache lifetimes, without shipping a client-side orchestration framework or coupling teams to a single origin.
#The Bottleneck: Origin-Coupled Composition
In a monolithic frontend, a single origin renders the full page and the CDN caches one object per URL. Once you split ownership across teams — navigation, product data, recommendations, footer — you either force all teams to render into one origin process (a distributed monolith with a shared release train), or you push composition to the client and accept the JavaScript execution tax.
Both options have measurable costs. Server-side composition in a single origin means one team’s slow database query blocks the entire page’s TTFB. Client-side composition means the browser cannot start painting until multiple round trips resolve, and any fragment failure becomes visible as a layout shift or a blank panel. Neither approach lets the CDN cache fragments at different TTLs — the header banner might be cacheable for an hour, the recommendation panel for thirty seconds, and the price block never at all.
Edge-side includes decouple these concerns. The CDN retrieves a base HTML document containing ESI directives, resolves each <esi:include> tag against its own origin (or against another cache tier), and stitches the result into a single response before it leaves the PoP. Each fragment carries its own cache-control semantics. The client receives one fully composed HTML document with zero additional round trips.

#Architectural Breakdown
An edge-side includes deployment has three logical tiers:
- Shell origin — serves the base template containing ESI markup. This is typically a thin, highly cacheable document with no user-specific data.
- Fragment origins — independent services, one per team or domain, each returning an HTML fragment with its own
Cache-ControlandSurrogate-Controlheaders. - Edge processor — the CDN or reverse proxy (Varnish, Fastly, Akamai) that parses ESI tags, issues sub-requests to fragment origins, and assembles the final response.
The critical architectural decision is cache-key granularity. The shell document’s cache key should be broad (path plus a small cookie set), while each fragment’s cache key should be scoped to only the parameters that affect that fragment. A recommendations fragment might vary on a user segment cookie; a navigation fragment might not vary on anything at all. Getting this wrong is the single most common cause of edge-side includes deployments underperforming client-side composition — over-broad fragment cache keys collapse hit ratios to near zero.
Rendering diagram...
#Implementation Logic
Rolling out edge-side includes for an existing micro-frontend estate follows a predictable sequence:
- Identify page regions with divergent cache lifetimes and ownership boundaries — these become fragment boundaries.
- Define a fragment contract: each fragment origin must return a self-contained HTML block with no reliance on shell-level JavaScript state, plus explicit
Surrogate-Controlheaders. - Configure the shell template with
<esi:include>directives, includingonerror="continue"fallbacks for graceful degradation. - Configure the edge processor (Varnish VCL, Fastly VCL, or Akamai property manager) to enable ESI processing and set per-fragment cache-key rules.
- Instrument fragment-level cache hit ratios separately from the shell hit ratio — they will diverge significantly and need independent alerting thresholds.
#Shell Template with Edge-Side Includes
1<!-- shell served with Surrogate-Control: max-age=3600 -->
2<!DOCTYPE html>
3<html>
4<head><title>Product Page</title></head>
5<body>
6 <esi:include src="/fragments/nav" onerror="continue"/>
7 <main>
8 <esi:include src="/fragments/product-details?sku=$(QUERY_STRING{sku})"/>
9 <esi:include src="/fragments/price?sku=$(QUERY_STRING{sku})" onerror="continue">
10 <esi:remove>
11 <p>Price unavailable</p>
12 </esi:remove>
13 </esi:include>
14 <esi:include src="/fragments/recommendations" onerror="continue"/>
15 </main>
16 <esi:include src="/fragments/footer"/>
17</body>
18</html>#Varnish VCL for ESI Processing
1sub vcl_backend_response {
2 if (bereq.url ~ "^/shell/") {
3 set beresp.do_esi = true;
4 set beresp.ttl = 1h;
5 }
6 if (bereq.url ~ "^/fragments/price") {
7 set beresp.ttl = 0s;
8 set beresp.uncacheable = true;
9 }
10 if (bereq.url ~ "^/fragments/recommendations") {
11 set beresp.ttl = 30s;
12 set beresp.http.Vary = "X-User-Segment";
13 }
14}
15
16sub vcl_recv {
17 if (req.url ~ "^/fragments/recommendations") {
18 set req.http.X-User-Segment = req.http.cookie:segment;
19 }
20}The do_esi = true flag tells Varnish to parse the response body for ESI tags and issue sub-requests through the same cache, meaning fragment responses benefit from the existing cache pool rather than requiring a separate fragment cache tier. Fastly’s implementation behaves similarly but exposes ESI control through beresp.do_esi in VCL with additional support for esi:vars interpolation, documented in the Fastly VCL reference. Teams building on the open standard should also review the original W3C ESI Language specification, since vendor implementations diverge on error-handling verbs and variable substitution syntax.

#Failure Modes and Edge Cases
Edge-side includes introduce failure surfaces that do not exist in monolithic rendering. The most common in production:
- Cascading sub-request timeouts — if a fragment origin degrades, the edge processor blocks assembly until its sub-request timeout expires. Without aggressive per-fragment timeouts (typically 200–400ms) a single slow origin inflates the composed response’s TTFB for every page using that fragment.
- Cache-key collision — two teams naming fragment query parameters identically (
?id=) can cause the edge processor’s cache-key normalisation to conflate unrelated fragments if the CDN config was copied across properties without adjustment. - Nested ESI depth limits — most CDNs cap nested include depth (Varnish defaults to five levels). Fragment-of-fragment architectures that exceed this silently truncate output rather than erroring loudly.
- Cookie leakage into cache keys — a fragment origin returning
Vary: Cookiewithout scoping to a specific cookie name will collapse hit ratios across the entire fragment tier, since every unique session becomes its own cache entry. - Partial-failure UX —
onerror="continue"prevents a fragment failure from breaking the whole page, but it also means broken fragments fail silently unless surrogate-side logging is wired to capture sub-request status codes.
#Decision Matrix: Composition Strategies
| Strategy | Assembly Location | Cache Granularity | Failure Isolation | Client JS Cost |
|---|---|---|---|---|
| Edge-side includes | CDN edge PoP | Per-fragment TTL/key | High (onerror fallback) | None |
| Module Federation | Browser runtime | Bundle-level only | Medium (error boundaries) | High |
| Iframe composition | Browser (isolated context) | Per-frame HTTP cache | Very high | Medium |
| Server-side monolith | Single origin | Page-level only | Low | None |
#Scaling and Security Trade-offs
Adopting edge-side includes shifts operational responsibility toward CDN configuration ownership, which has consequences worth weighing against the alternatives discussed in these architectural patterns for distributed frontends:
- Scaling gains — fragment origins scale independently and can be deployed on entirely different stacks; a Node.js recommendations service and a Java pricing service coexist behind the same shell without coordination.
- Reduced client compute — no bundle parsing or hydration cost for composition logic, which matters disproportionately on low-end mobile devices where JavaScript execution, not network, dominates render time.
- Vendor lock-in risk — ESI dialect differences between Varnish, Fastly and Akamai mean fragment contracts are not portable across CDN vendors without a compatibility shim.
- Expanded attack surface — sub-requests to fragment origins bypass client-facing WAF rules unless the edge processor re-applies inspection on each internal fetch; SSRF-style risks emerge if fragment
srcattributes accept unsanitised query input. - Cache poisoning exposure — because assembly happens before the response is cached at the shell level, a malformed fragment response can poison the shell cache entry for every subsequent visitor until TTL expiry, making fragment-origin input validation non-negotiable.
- Debuggability cost — request tracing across shell, edge processor, and multiple fragment origins requires surrogate-side header propagation (e.g. injecting a shared trace ID before sub-requests fire) that most teams underinvest in until an incident forces the issue.
Edge-side includes are not a universal replacement for client-side composition; they suit content-heavy, cache-friendly page structures far better than highly interactive, stateful UI regions. The pragmatic pattern in most estates we have reviewed is hybrid: ESI for the outer shell and largely static regions, with a lightweight client-side island reserved for genuinely interactive fragments such as cart state or live pricing widgets that cannot tolerate edge-cache staleness.
Evidence trail
Sources and verification
Primary documentation and external technical references used in this article.
Comments
Add a thoughtful note on Edge-Side Includes: Composing Micro-Frontends. Comments are checked for spam and held for moderation before appearing.
Related articles
Software Architecture
A Bounded Recovery Path for API-Driven Software Architecture Changes
How to design, validate and recover one bounded API architecture change with explicit evidence, bounded failure containment and a fixed rollback path.
Software Architecture
Building a Failure-Aware API Workflow for Software Architecture
A bounded, failure-aware pattern for implementing a software architecture workflow on an API, with explicit validation stages, failure containment and a defined rollback ladder.
Software Architecture
Engineering a Bounded API Workflow for Predictable Architecture
A bounded, evidence-led API workflow design covering architecture, implementation, validation, failure modes, security boundaries and a reversible rollback path for an isolated validation environment.
Software Architecture
Building a Recoverable API Workflow for Software Architecture Reliability
A bounded, evidence-led approach to introducing and safely recovering a single API-mediated architectural change, using a routing boundary as the containment mechanism.
Learn More About KBY
About KBY
Learn about our mission, editorial standards, and commitment to trusted engineering knowledge.
Why Trust KBY
Explore the processes and policies that ensure our publications are accurate, useful, and responsible.
Newsletter
Get our latest editorial publications, research and practical insights sent directly to your inbox.
Was this useful?
Engineering insights, direct to you.
Receive the latest Systems Engineering tutorials, production guides, Engineering Labs and operational best practices.