Skip to main content
Systems Engineering

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

In this guide

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.

Evidence trail

Sources and verification

Primary documentation and external technical references used in this article.

  1. 01Fastly VCL referencedeveloper.fastly.com
  2. 02W3C ESI Language specificationw3.org
Eleanor Hayes

Eleanor Hayes

Systems Engineering Editor

Dr Eleanor Hayes is a veteran cryptography researcher and enterprise security architect specialising in zero-trust network implementations.

Published Last changed
View 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...
Comment submission is disabled until Cloudflare Turnstile keys are configured.

Learn More About KBY

Was this useful?

Engineering insights, direct to you.

Receive the latest Systems Engineering tutorials, production guides, Engineering Labs and operational best practices.