If you’re using Adobe Experience Manager as a Cloud Service (AEMaaCS), you’ve likely wondered what to do with your existing CDN. AEMaaCS includes a fully managed CDN with caching, WAF, and DDoS protection. But it also supports a Bring Your Own CDN model.
This flexibility allows you to layer your CDN in front of Adobe’s, boosting page speed through edge caching.
The Challenge: Static vs. Dynamic Content
Many AEM pages combine static and dynamic components, and delivering both types of content through multiple layers of CDN can become a complex process.
Imagine a page filled with static components and just a few dynamic ones. For performance, the static content should be cached heavily. But dynamic components often require real-time rendering and can’t be cached. Since caching is typically controlled by page path—both in Dispatcher and the CDN—we end up disabling caching for the entire page. This workaround ensures dynamic components work as expected, but it undermines the purpose of caching and fast delivery.
Sling Dynamic Includes Provides a Partial Solution
AEM provides Sling Dynamic Includes (SDI) to partially cache the static content and dynamic content using placeholder tags. When a request comes in, it merges the static and dynamic content and then delivers it to the customer.
You can learn more about Sling Dynamic Include on the Adobe Experience League site.
However, SDI relies on the Dispatcher server for processing. This adds load and latency.
Imagine if this process is done on the CDN. This is where Edge Side Includes (ESI) comes into play.
Edge-Side Includes Enters the Chat
ESI does the same thing as SDI, but it uses ESI tags on the cached pages on the CDN.
ESI is powerful, but what if you want to do additional custom business logic apart from just fetching the content? That’s where Cloudflare Workers shines.
What is Cloudflare Workers?
Cloudflare Workers is a serverless application that can be executed on the CDN Edge Network. Executing the code in edge locations closer to the user location reduces the latency and performance because the request does not have to reach the origin servers.
Learn more about Cloudflare Workers on the Cloudflare Doc site.
ESI + Cloudflare Workers
In the following example, I’ll share how Cloudflare Workers intercepts ESI tags and fetches both original and translated content.
How to Enable ESI in AEM
- Enable SDI in AEM Publish: /system/console/configMgr/org.apache.sling.dynamicinclude.Configuration
- Add mod_include to your Dispatcher config.
- Set no-cache rules for SDI fragments using specific selectors.
Note: Set include-filter.config.include-type to “ESI” to enable Edge Side Includes.
Visit this article for more detailed steps on how to enable SDI, Dispatcher configuration.
Writing the Cloudflare Worker Script
Next, write a custom script to intercept the ESI tag request and make a custom call to the origin to get the content, either from the original or translated content.
addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request){ //You can update the url and set it to your local aem url in case of local development const url = new URL(request.url); const origin = url.origin; // You can modify the headers based on your requirements and then create a new request with the new headers const originalHeaders = request.headers; const newHeaders = new Headers(originalHeaders); //Append new headers here const aemRequest = new Request(url, { headers: newHeaders, redirect: 'manual', }); // Get the response from the Origin try{ const aemresponse = await fetch(aemRequest); // Get the content type const contentType = aemresponse.headers.get("Content-Type") || ""; // If the content type is not “text/html”, return the response as usual or as per requirement, else check if the content has any “esi:include” tag If(!contentType.toLocaleLowerCase().includes("text/html")){ //return } //fetch the HTML response const html = await aemresponse.text(); if(!html.includes("esi:include")){ //content doesn’t have esi tag, return the response } return fetchESIContent(aemresponse, html, origin) } } async function fetchESIContent(originResponse, html, origin) { try{ //RegEx expression to find all the esi:include tag in the page const esiRegex = /<esi:include[^>]*\ssrc="([^"]+)"[^>]*\/?>/gi; //fetch all fragments and replace those const replaced = await replaceAsync(html, esiRegex, async (match, src) => { try { const absEsiUrl = resolveEsiSrc(src, origin); const fragRes = await fetch(absEsiUrl, {headers: {"Cache-Control" : "no-store"}}); console.log('Fragment response',fragRes.statusText) return fragRes.ok ? await fragRes.text() : "Fragment Response didn't return anything"; } catch (error) { console.error("Error in fetching esi fragments: ",error.message); return ""; } }) const headers = appendResponseHeader(originResponse) // Add this header to confirm that ESI has been injected successfully headers.set("X-ESI-Injected", "true"); return new Response(replaced, { headers, statusText: originResponse.statusText, status: originResponse.status }) } catch(err){ new Response("Failed to fetch AEM page: "+ err.message, {status: 500}) } } // Function to fetch content asynchronously async function replaceAsync(str, regex, asycFn) { const parts = []; let lastIndex = 0; for( const m of str.matchAll(regex)){ //console.log("ESI Part of the page:: ",m) parts.push(str.slice(lastIndex, m.index)); parts.push(await asycFn(...m)); lastIndex = m.index + m[0].length; } parts.push(str.slice(lastIndex)); return parts.join(""); }
Bonus Tip: Local Testing With Miniflare
Want to test Cloudflare Workers locally? Use Miniflare, a simulator for Worker environments.
Check out the official Miniflare documentation.
You Don’t Need to Sacrifice Performance or Functionality
Implementing ESI through Cloudflare Workers is an excellent way to combine aggressive caching with dynamic content rendering—without compromising overall page performance or functionality.
This approach helps teams deliver faster, smarter experiences at scale. As edge computing continues to evolve, we’re excited to explore even more ways to optimize performance and personalization.