Klish Group is a consulting firm specializing in Web and Digital Content Management strategy, design and implementation

Customer Support
info@klishgroup.com
312.546.4727

Moving Edge Side Includes Off Akamai

Project Spotlight: rebuilding a proprietary CDN rendering layer as a 4 ms native resolver on the origin.

The Hardest Part of Leaving a CDN Isn't the CDN

A global equipment manufacturer wanted to consolidate its public website onto AWS and retire Akamai. Most of what Akamai did had a direct AWS equivalent: bot management, geolocation, thousands of redirect rules, edge caching. One piece did not. Every page on the site, across sixteen locales and roughly 710 templates published from OpenText TeamSite, was assembled at Akamai's edge using Edge Side Includes (ESI), and nothing else could render it.

Klish Group researched the options, built three working proofs of concept against the customer's real pages, and delivered the one that won: a native ESI resolver that now renders every regional site at the origin.

At a Glance

39 ESI Constructs In Use

12 directives, 14 expression functions and 13 variable types, and only one of them, esi:include, is part of the W3C ESI standard.

Three Working Prototypes

Apache Traffic Server, a native Rust resolver, and Fastly Compute, each built and run against the customer's real pages before anyone chose.

About 4 ms Per Page

Full page assembly, including a 124 KB header with the breadcrumb logic, compared with 27 ms for an optimized Python resolver.

No New Vendor

Built on an MIT-licensed open-source engine and run on infrastructure the customer already owns, with no platform contract and no per-request fee.

The Challenge: A Dialect, Not a Standard

TeamSite published finished HTML files to an Apache origin, and Apache served them untouched. The templates carried ESI markup that Akamai evaluated at the edge on its way to the visitor. Over the years the templates had come to depend on Akamai's proprietary extensions: variable assignment, loops with break, string and date functions, regular-expression matching, computed map lookups, and a custom directive that pulls JSON data into a page.

The site's breadcrumb shows how far that went. It was not stored anywhere. It was computed on every request, at the edge, from the visitor's URL, by around 150 lines of ESI that loop over path segments and check each one against a list of roughly 600 exclusion patterns and 200 label overrides.

Moving that logic into client-side JavaScript was ruled out early. These are SEO-critical product pages, and the breadcrumb feeds structured data, page speed scores and link previews on social media. The pages had to arrive finished.

Every Page Depends on It

The header and footer, which every page includes, both used Akamai-only constructs. Leaving even one page on standard ESI was not an option.

Includes Within Includes

Product and listing pages resolve data in chains: a product page loads an offers file, which points to offer folders, which supply detail-card fragments, each with its own ESI.

No Change for Authors

Content authors had to keep publishing from TeamSite exactly as they always had. Rewriting 710 templates was not an option either.

What We Evaluated

We started by measuring how much of the customer's ESI each off-the-shelf processor could actually handle.

Processor Coverage of the dialect Verdict
Varnish (open source) About 3%: include, remove, comment Ruled out
Varnish Enterprise About 15-20%; adds conditionals and variables, but no loops or evaluation Ruled out, and commercially licensed
NGINX SSI / nginx-esi Less than Varnish Ruled out
Apache Traffic Server ESI plugin About half the directives, no expression functions, no loops Best off-the-shelf option: prototyped
Open-source fastly/esi engine Full dialect with targeted patches Prototyped twice: on Fastly's edge and natively on the origin

Option A: Apache Traffic Server

ATS assembles includes in front of Apache. Because its plugin covers only half the dialect, a custom Python pre-resolver evaluates everything else first. Two rendering components working together.

Option B: Native Rust on the Origin

One compiled resolver does all the ESI work on each web server: evaluates the dialect, fetches every include, and returns finished HTML. CloudFront is the only cache.

Option C: Fastly Compute

The same open-source engine compiled to WebAssembly and run on Fastly's edge (Fastly Compute), between CloudFront and the origin. Pages are assembled near the visitor.

What the Prototypes Revealed

Apache Traffic Server looked like the safe choice: a mature, open-source caching proxy with a real ESI plugin. Running the customer's actual templates through it told a different story.

  • It fails silently. A single unsupported directive anywhere in a template returns HTTP 200 with an empty body. No error, no warning, no log line.
  • Error handling doesn't catch it. ESI's own try/except fires only when an include fails to load, not when the parser rejects the template.
  • One bad fragment breaks the whole page. A clean page that includes a fragment with unsupported syntax is cut off at the include. Since every page includes the header and footer, the whole site was effectively unsupported.
  • It was unstable under test. We reproduced a plugin crash and a cache-poisoning state that could only be cleared by rebuilding the container.

The pre-resolver worked: with it in place ATS rendered every page type correctly. But it meant writing a full ESI evaluator anyway and then running a second, more fragile engine behind it. Once a complete evaluator was needed regardless, the case for ATS came down to its in-region cache. With a single-region origin, that cache cannot hide cross-region latency on a miss. Only CloudFront can.

Fastly's open-source fastly/esi engine, patched for the Akamai dialect, handled all 12 directives and 14 functions. That left a question of where to run it: on Fastly's paid edge, or natively on the customer's own servers.

Side by Side

  A: Traffic Server B: Native Rust C: Fastly Compute
Where pages are assembled Origin Origin Fastly's global edge
Rendering components Two (Python + ATS plugin) One One, on a vendor platform
Unsupported syntax Silent empty page Explicit, handled errors Explicit, handled errors
Compute per cache miss ~27 ms + ATS assembly ~4 ms Runs at the edge, billed per request and CPU time
Cache invalidation on publish CloudFront plus a purge to every web server CloudFront only CloudFront plus Fastly
Vendor dependency None None A second commercial CDN

We recommended Option B. It removed the failure modes we had found, it ran faster than either alternative, and it didn't replace one CDN vendor with another, which was the whole point of the migration.

What We Built

We forked the MIT-licensed fastly/esi crate and removed its dependency on Fastly's platform SDK. Its parser, expression evaluator and function library were already separate from the platform code, so the correctness-critical logic was kept unchanged. On top of it we added patches for the Akamai dialect, support for the custom JSON directive, and a native orchestrator that fetches and inlines every include.

The result is a single static Linux binary that runs as a small service on each web server. Apache passes page requests to it over the local network interface; it reads the published templates from disk, resolves every directive and include, and hands back finished HTML with no ESI left in it.

flowchart TD V[Visitor] --> CDN[CDN edge cache] CDN -->|cache miss| AP[Apache origin] AP -->|page request, loopback| R[Rust ESI resolver] R -->|reads templates and fragments| FS[(TeamSite-published HTML)] R -->|finished HTML, no ESI| AP AP --> CDN
Resolver Full page assembly
Python, first version 190 ms
Python, optimized 27 ms
Native Rust ~4 ms

Rolled Out One Site at a Time

A Switch Per Hostname

The web servers generate their Apache configuration from a plain-text site list. One flag on a hostname's line moves that site to the resolver, or moves it back.

Region by Region

Dedicated test hostnames first, then each regional site in turn over about eight weeks, until every site in every environment was served by the resolver.

Tuned From Live Traffic

The resolver spends most of its time waiting on includes, not computing, so we sized its worker pool for concurrency rather than CPU count, then adjusted it based on measured queueing and tail latency.

What We Took Away

  • Measure before you choose. On paper, Apache Traffic Server was the obvious answer. Only running the real templates through it exposed the silent empty pages.
  • Reuse the hard part. We didn't write an ESI parser from scratch. A well-maintained open-source engine supplied the correctness-critical logic, and our work went into the gaps.
  • Fewer moving parts wins. One small binary we control replaced a proprietary edge feature without adding a second CDN, a second cache to purge, or a new vendor contract.
  • Make cutover reversible. A per-site switch let the team move traffic gradually and step back instantly if anything looked wrong.

Planning a CDN migration? Let's find the hard parts early.

Contact Us