Back to all questions

How to Migrate from Akamai ESI?

Edward Tsinovoi
CDN Migration
May 16, 2025

To migrate from Akamai ESI, you’ll need to audit where you use ESI, understand why it’s there, and replace it with something modern—usually edge functions (like Cloudflare Workers), server-side rendering (SSR), or client-side rendering (CSR). Serverless edge compute is your best like-for-like replacement, especially for dynamic edge caching and content assembly.

Maybe it once helped you cache fragments at the edge and save origin compute, but these days, you’ve got way better tools. ESI’s getting deprecated, it’s hard to debug, and it’s a security risk. It’s time to move:

What is Edge Side Includes (ESI)?

Edge Side Includes (ESI) was Akamai’s way of letting you build pages out of smaller, cacheable parts—at the edge. Instead of waiting for your origin server to generate an entire HTML page, you could offload that work to the CDN by including dynamic fragments like this:

<esi:include src="/fragments/user-banner.html" />

That tag told Akamai’s edge server: “Insert the contents of this URL right here, before sending it to the user.”

It sounds simple, but it was powerful. You could:

  • Split page logic into cacheable and personalized chunks
  • Set different cache rules per fragment (long TTL for headers, short for ads)
  • Inject personalization based on cookies, headers, or geo
  • Run conditional logic with <esi:choose>, <esi:vars>, or <esi:assign>

So why did we use ESI? Because it let us serve dynamic-ish pages fast, without blowing up the origin or disabling CDN caching.

Reasons for Migrating from Akamai ESI

All of the ESI goodness came at a cost:

  • You were locked into Akamai’s tooling (Property Manager, specific config behaviors)
  • Debugging was painful, often involving proprietary tools and cryptic logs
  • Security was a constant concern — ESI Injection, SSRF, and XSS were real threats
  • Performance had a ceiling — parsing and assembling includes at the edge added latency under load

It was a clever solution for its time, but web infrastructure evolved. And now we’ve got edge functions, modern rendering frameworks, and finer-grained control—without relying on an XML-based syntax or a tightly coupled CDN layer.

If you're still using ESI, you're probably carrying legacy tech debt. 

Step 1: Inventory What You’re Using ESI For

Before you rip anything out, make a list:

  • Where are you using ESI? Pages, components, apps.
  • What tags are in play? Simple includes or complex logic?
  • Are they personalized? Conditional? Cached differently?

What you're really asking is: “What’s this fragment doing?”

If it’s a static include (like a footer), great—you can replace it easily. If it’s dynamic (like personalized offers), we’ll need logic somewhere.

I ran a scan across templates and headers to flag all the <esi:*> tags. Manual inspection still caught some, so don’t skip code reviews.

Step 2: Choose the Right Replacement

You’ve got three primary paths, depending on what your ESI does:

Use Case Best Replacement
Simple includes (static headers, navs) Edge function or static build
Conditional includes (user-type-based content) Edge logic (Cloudflare Workers, Lambda@Edge)
Personalized content (cookie-driven, API calls) SSR or edge function
JavaScript widgets or dynamic UIs CSR (React/Vue)

Now let's break down how each one works.

Option 1: Edge Functions (My Top Pick)

Think of it as ESI 2.0. Instead of XML, you're writing code (usually JS or Rust). You can:

  • Inspect headers, cookies, query strings

    Note: Some edge platforms (like Cloudflare Workers) require explicitly forwarding cookies in your cache or fetch configuration. Double-check your CDN settings to make sure cookies reach your logic.
  • Fetch external content
  • Assemble the response
  • Cache it smartly at the edge

But, don’t forget to handle fallback logic. If your original ESI used alt attributes or onerror="continue", replicate that with try/catch logic or fetch fallbacks. You can also log or tag failed fragments to help with debugging.

If you're already on Akamai, look into EdgeWorkers. Otherwise, Cloudflare Workers or Fastly Compute@Edge are my go-to.

Example (Cloudflare Worker):

addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request));
})

async function handleRequest(request) {
  const userType = getCookie(request.headers.get("cookie"), "user_type");
  const fragmentUrl = userType === "premium"
    ? "https://example.com/premium.html"
    : "https://example.com/standard.html";

  const mainPage = await fetch("https://example.com/main");
  const fragment = await fetch(fragmentUrl);

  return new HTMLRewriter()
    .on("#offer-slot", {
      element(element) {
        element.setInnerContent(await fragment.text(), { html: true });
      }
    })
    .transform(mainPage);
}

Way more control. And you don’t have to worry about XML quirks or Akamai’s custom syntax.

Bonus: serverless edge logic is portable across platforms. Stick to standard JS/TS and avoid deep vendor-specific APIs if you want to stay flexible.

Always validate URLs, sanitize dynamic data, and lock down what your edge function can fetch. Unlike ESI, you’re writing actual code now—SSRF is still a risk if you’re blindly fetching from untrusted inputs.

Option 2: Server-Side Rendering (SSR)

Use this if:

  • SEO is critical
  • You need dynamic content rendered before delivery
  • Your app is already server-heavy

Let’s say you're using Next.js. Replace your <esi:include> with a fetch inside getServerSideProps. Pass that data into a React component.

export async function getServerSideProps(context) {
  const userType = context.req.cookies.user_type;
  const offerUrl = userType === 'premium'
    ? '/api/premium-offer'
    : '/api/standard-offer';

  const offer = await fetch(offerUrl).then(res => res.text());

  return {
    props: { offerHtml: offer },
  };
}

SSR hits origin more, but lets you do everything in one place—and it’s fully visible to crawlers.

Option 3: Client-Side Rendering (CSR)

This is what I use for dynamic widgets that don’t need to show up immediately or for SEO.

Instead of <esi:include>, I’ll put a <div id="related-products"/> and hydrate it on the client with React or Vue.

useEffect(() => {
  fetch(`/api/related-products?productId=${productId}`)
    .then(res => res.json())
    .then(setProducts);
}, []);

It’s lighter on the server and CDN, but slower for first paint. Don’t use this for things users expect to see instantly.

Step 3: Handle Caching Intentionally

One thing ESI nailed was letting you cache fragments differently. If you skip that now, performance will tank. Here's how I tackled it:

  • Edge Functions: Set cache headers directly—Cache-Control, Surrogate-Control, and use the edge platform’s caching APIs (caches.default.put() in Cloudflare Workers, for example). You can also implement logic to skip cache for logged-in users or vary by cookie.
  • SSR: Cache the final HTML with headers, and use Vary: Cookie or Vary: User-Agent if you're personalizing content. For data-heavy SSR, I sometimes cache API responses separately at the app level (Redis, edge KV stores, etc.).
  • CSR: You’re caching API responses now. Make sure you set appropriate headers on those endpoints. Also consider client-side strategies—localStorage, sessionStorage, or service workers for repeat visits.

This step isn’t optional. ESI gave you edge caching for free. You need to rebuild that foundation—intentionally.

Step 4: Start Small, Validate, Scale

Don’t try to replace everything in one go. I picked a single, low-risk ESI include—something like a static footer or marketing banner.

Then I asked:

  • Does it render correctly?
  • Did the cache hit?
  • Was the latency better, worse, or the same?

Once that was green-lit, I built a list of all other ESI usages and sorted them by complexity. Anything dynamic or cookie-based came later. That way, I caught surprises early—and built momentum.

Step 5: Test Like You Mean It

Debugging ESI sucks. Migrating off it is a chance to modernize how you test and troubleshoot.

Here’s what I leaned on:

  • Edge dev tools: Cloudflare Wrangler, Fastly Viceroy—they let you run and test edge logic locally.
  • Browser dev tools: For CSR, just inspect the DOM and network like normal.
  • CDN logs: Always inspect your CDN logs for cache headers, response times, and edge behavior
  • End-to-end tests: I set up Playwright to confirm fragment rendering and simulate cookie logic. If your new setup breaks under real-world conditions, tests will catch it before users do.

Get your baseline metrics (TTFB, cache hit rate, LCP) before and after the switch. If possible, use preview environments or traffic mirroring to test your new implementation side-by-side with ESI. 

You can shadow traffic and log differences without impacting real users.

Step 6: Go Live, Monitor, Iterate

Once it’s deployed, I don’t just move on—I monitor three things closely:

  • Origin Offload – Is the edge doing the heavy lifting, or are you hammering your origin again?
  • Latency – Are users getting content faster or slower?
  • Breakages – Any fragments not showing up, caching behaving weird, personalized logic failing?

If your ESI was managed through Akamai’s UI, this is your chance to switch to modern CI/CD. Deploy edge functions, SSR, or CSR builds through your pipeline—version them, roll back easily, and integrate tests right into the flow.

I use a mix of:

You’re replacing invisible logic with new code, so stay on top of it.