Cache Invalidation in Hybrid AEM: Keeping EDS and AMS
Cache Invalidation in Hybrid AEM: Keeping EDS and AMS in Sync on AWS CloudFront
Introduction
In a hybrid AEM setup where EDS and AMS serve different parts of the same website through AWS CloudFront, cache invalidation is one of the trickiest problems to solve. Both systems have completely different invalidation mechanisms — and if you don't coordinate them properly, editors end up seeing stale content, confused about why their published changes aren't showing up.
This post explains how cache invalidation works in each system, why hybrid setups make it harder, and how to build a reliable invalidation strategy across both origins.
The Core Problem
In a single-origin AEM setup, invalidation is straightforward:
- Editor publishes in AEM
- Dispatcher flush agent clears the Dispatcher cache
- CloudFront invalidation clears the CDN layer
- Done
In a hybrid setup you have two completely separate invalidation pipelines that must never interfere with each other:
EDS publish event
↓
Franklin Bot → Hlx Purge API → EDS edge cache cleared
↓
CloudFront invalidation for /blog/* paths only
AMS publish / replication event
↓
Dispatcher Flush Agent → Dispatcher cache cleared
↓
CloudFront invalidation for /products/* paths only
The danger: if an AMS invalidation accidentally triggers a CloudFront purge on EDS paths (or vice versa), you lose your entire EDS cache — destroying the performance advantage EDS was chosen for.
How EDS Invalidation Works
EDS has a very elegant, automated invalidation model.
When an author updates a page in Google Docs or SharePoint and it is published through the Franklin pipeline:
- Franklin Bot detects the change
- It automatically calls the Hlx Purge API for the affected URL
- The EDS edge cache (Fastly/Hlx) clears that specific page
- CloudFront, sitting in front of EDS, needs to be told separately
The key thing to understand: EDS invalidates at the page level automatically. You rarely need to manually trigger EDS purges. The challenge is making sure CloudFront also clears its copy.
EDS invalidation scope:
- Single page purge — when one document is updated
- Section purge — when a shared block or fragment is updated (affects multiple pages)
- Full site purge — rarely needed, triggered manually
How AMS Invalidation Works
AMS invalidation follows the traditional Dispatcher flush model.
When an author activates/publishes content in AEM:
- AEM replication agent sends the content to Publish instance
- Dispatcher Flush Agent on Publish sends a flush request to Dispatcher
- Dispatcher removes the cached
.htmlfile and marks the stat file - CloudFront needs a separate invalidation call for the CDN layer
The key difference from EDS: AMS invalidation is path-based and granular — it clears specific .html files or entire subtrees depending on your Dispatcher flush configuration. It does not automatically clear CloudFront.
AMS invalidation scope:
- Single page —
/content/mysite/en/products/overview.html - Section tree —
/content/mysite/en/products/* - Full Dispatcher flush — clears everything (avoid in production)
The CloudFront Invalidation Layer
Both EDS and AMS need to trigger CloudFront invalidations when content changes. But they must only invalidate their own paths.
The golden rule:
EDS publish event → invalidate CloudFront /blog/* paths only
AMS publish event → invalidate CloudFront /products/* paths only
Never let an EDS event invalidate AMS paths and never let an AMS flush invalidate EDS paths. A full /* CloudFront invalidation should be a last resort — it clears everything across both origins and destroys cache efficiency.
How to Trigger Scoped CloudFront Invalidations
From EDS side: EDS does not natively call CloudFront. You need a small webhook or Lambda function that:
Listen for Franklin publish webhook event
Extract the URL path that was published (e.g. /blog/my-post)
Call CloudFront CreateInvalidation API
Scope: only the specific EDS path (/blog/my-post)
Do NOT invalidate /products/* or AMS paths
From AMS side: AEM has a built-in mechanism for this — a custom CQ Replication Event Listener or a post-processing workflow step that:
Listen for AEM replication/activation event
Get the page path that was activated (e.g. /content/mysite/en/products/overview)
Map it to the external URL (/products/overview)
Call CloudFront CreateInvalidation API
Scope: only the specific AMS path
Do NOT invalidate /blog/* or EDS paths
Invalidation Patterns — What Works and What Doesn't
Pattern 1 — Page-Level Invalidation (Recommended)
Invalidate only the exact URL that changed.
Page published: /content/mysite/en/products/camera.html
External URL: /products/camera
CloudFront invalidation: /products/camera
Pros: Precise, fast, cheap (CloudFront charges per invalidation path) Cons: If a shared component (header, footer, navigation) changes, you need to invalidate all pages that use it
Pattern 2 — Section-Level Invalidation
Invalidate an entire section when a shared component or template changes.
Navigation component updated → affects all pages
CloudFront invalidation: /products/*
Pros: Covers all affected pages in one call Cons: Temporarily reduces cache hit rate for entire section
Pattern 3 — Surrogate Keys / Cache Tags (Advanced)
Tag CloudFront responses with logical groups so you can invalidate by tag rather than by path.
All /products/* pages tagged with: "products-section", "global-nav"
Navigation changes → invalidate tag "global-nav"
CloudFront clears all pages with that tag across /products/*
This is the most precise approach but requires CloudFront to pass surrogate key headers from AMS Dispatcher (Surrogate-Key or Cache-Tag headers) and a Lambda@Edge function to manage tag-based invalidation.
Shared Components — The Hardest Invalidation Problem
In hybrid setups, the most painful scenario is a shared component that appears on both EDS and AMS pages — typically the global header, footer, or navigation.
Global navigation updated in AEM
↓ Needs to invalidate:
/products/* (AMS pages) AND
/blog/* (EDS pages)
Recommended approach:
Treat shared components as two separate problems:
- On AMS pages — navigation is rendered server-side by AEM. AMS invalidation handles it.
- On EDS pages — navigation is typically a shared block fetched from a Google Sheet or SharePoint list. EDS automatically purges it when the sheet is updated.
The key is: do not try to share a single invalidation event across both origins. Keep them independent and let each system handle its own shared components natively.
Stale Content Fallback — Defence in Depth
Even with the best invalidation setup, there will be edge cases where CloudFront serves stale content. Use stale-while-revalidate and stale-if-error directives as a safety net:
For EDS paths:
Cache-Control: public, max-age=86400, stale-while-revalidate=3600
This means: serve cached content for up to 24 hours, but if the cache is between 24h and 25h old, serve stale while fetching fresh in the background. The user never sees a slow page.
For AMS paths:
Cache-Control: public, max-age=3600, stale-while-revalidate=300, stale-if-error=86400
This means: serve cached content for 1 hour, revalidate in background for 5 minutes after expiry, and if AMS Dispatcher is down, serve stale content for up to 24 hours rather than showing a 503.
Key Takeaways
- EDS and AMS have completely separate invalidation pipelines — never let them cross-contaminate each other's CloudFront paths.
- EDS invalidation is largely automatic via Franklin Bot — your job is to wire up a CloudFront invalidation scoped to EDS paths only.
- AMS invalidation requires a custom replication listener or workflow step to trigger scoped CloudFront invalidations.
- Always invalidate at page level where possible — section-level invalidation as a fallback, full
/*invalidation only as a last resort. - Use
stale-while-revalidateas a safety net on both origins — it eliminates slow cache misses and protects against origin downtime. - Shared components (navigation, footer) should be invalidated independently on each origin — do not try to build a single cross-origin invalidation event.
Comments
Post a Comment