Skip to main content

Adobe

AEM and Cloudflare Workers: The Ultimate Duo for Blazing Fast Pages

AEMaaCS and Cloudflare Workers for Fast Pages

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

  1. Enable SDI in AEM Publish: /system/console/configMgr/org.apache.sling.dynamicinclude.Configuration
  2. Add mod_include to your Dispatcher config.
  3. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Kazi Maidul Haque, Lead Technical Consultant

Maidul is an Adobe Certified Architect who brings 12 years of IT experience, with 9+ years as an AEM developer and Architect.  His profound knowledge of Adobe Marketing Cloud Products allows him to deliver comprehensive digital solutions. Committed to continuous learning, Maidul excels in mentoring colleagues and client staff. A staunch advocate of teamwork, he remains keenly attuned to the latest industry trends, ensuring cutting-edge results for every project.

More from this Author

Follow Us