Skip to main content
KBY Technologies Logo
root/software-architecture/edge-side-includes-composing-micro-frontends

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.
Edge-Side Includes: Composing Micro-Frontends
18/07/2026|9 min read|
Eleanor HayesAnalysis byEleanor Hayes

Architecture at a glance

ScopeSoftware Architecture
What it coversThe Bottleneck: Origin-Coupled Composition · Architectural Breakdown · Implementation Logic
Practical depth3 implementation examples
Share

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.

Edge-Side Includes: Composing Micro-Frontends architecture diagram 1

#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-Control and Surrogate-Control headers.
  • 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-Control headers.
  • Configure the shell template with <esi:include> directives, including onerror="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.

Edge-Side Includes: Composing Micro-Frontends architecture diagram 2

#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: Cookie without 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 UXonerror="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

StrategyAssembly LocationCache GranularityFailure IsolationClient JS Cost
Edge-side includesCDN edge PoPPer-fragment TTL/keyHigh (onerror fallback)None
Module FederationBrowser runtimeBundle-level onlyMedium (error boundaries)High
Iframe compositionBrowser (isolated context)Per-frame HTTP cacheVery highMedium
Server-side monolithSingle originPage-level onlyLowNone

#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 src attributes 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.

Sources and verification

Primary documentation and external references used in this analysis.

Eleanor Hayes

Written By

Eleanor Hayes

Dr Eleanor Hayes is a veteran cryptography researcher and enterprise security architect specialising in zero-trust network implementations. Having spent a decade securing critical national infrastructure, she now designs identity-aware proxy perimeters and mutual TLS topologies for highly distributed environments. She holds multiple GIAC certifications and regularly consults on mitigating advanced persistent threats within microservice ecosystems.
View author profile →
Reader Interaction

Comments

Add a thoughtful note on Edge-Side Includes: Composing Micro-Frontends. Comments are checked for spam and held for moderation before appearing.

Loading comments...

Loading anti-spam check...