Edge Delivery Services · 15 of 29
The six architectural approaches for using AEM Edge Delivery Services — pure DA, Universal Editor on AEM, AEM as a headless JSON backend, Content Fragments with GraphQL, external headless, and the hybrid same-domain pattern — with the approach we chose on the POC and an honest answer to "why pay for AEM and EDS?"
This chapter covers:
By this point in the series we have covered what EDS is, how blocks work, how authoring connects to delivery, what EDS can and cannot do, and the architecture decision framework in Chapter 14.
But one question keeps coming up in real projects, and it is usually asked by an architect or a budget owner rather than a developer:
We already pay for AEM. Why would we add EDS? Isn't that spending money twice?
And the related engineering question:
There seem to be several ways to wire EDS to AEM. Which one do we use, and why?
This chapter answers both. It is deliberately a synthesis chapter. Individual facts appear in earlier chapters — content storage in Chapter 13, the capability comparison in Chapter 12, the limitations framework in Chapter 14. Here we put them next to each other so the choice, and the money argument, are explicit.
Before the approaches, hold one idea in your head.
EDS separates content from delivery. A page is assembled in the browser from small pieces of content plus block JavaScript and CSS. That content can come from anywhere that returns HTML or JSON:
da.live)So "using EDS" is not one architecture. It is a delivery tier that can be fed by several different content sources. The approaches below are simply which source feeds which part of the page.
| # | Approach | Where content lives | Who authors | AEM needed? |
|---|---|---|---|---|
| A | Pure EDS + DA | da.live document store | Business users in Google Docs / Word / DA | No |
| B | EDS + Universal Editor on AEM (xwalk) | AEM JCR | Authors in the Universal Editor | Yes |
| C | AEM as a headless JSON backend | AEM JCR, exposed as JSON | Authors in AEM (classic) | Yes |
| D | Content Fragments + GraphQL | AEM JCR (structured CF models) | Authors in the CF editor | Yes |
| E | External headless CMS + EDS | Contentful / Hygraph / etc. | Authors in that CMS | No |
| F | Hybrid same-domain | Mixed — EDS and AEM on one domain | Both, per section | Yes |
The rest of the chapter walks each one, then chooses.
The default EDS model. Content is authored as documents in DA (da.live), Preview
promotes it to the Content Bus, and blocks render it. No AEM instance is involved.
Where it fits: editorial sites — marketing, campaigns, product docs, news.
What you get: the lowest cost, the fastest authoring onboarding (it is a document), and the best structural Core Web Vitals.
What you give up: JCR, workflow, MSM, Content Fragments, DAM at scale, per-node ACLs — everything covered in Chapter 14.
Who pays: EDS entitlement only. No AEM license required.
Content lives in AEM's JCR, authors edit it in the Universal Editor, and it is
delivered through the EDS pipeline (bin/franklin.delivery). This is the "keep
AEM, gain EDS delivery" path covered in Chapter 18.
Where it fits: you already run AEM, your team manages content in JCR, and you want EDS performance without moving content out of AEM.
What you get: EDS delivery speed plus the AEM capabilities that survive in xwalk — workflow, MSM, DAM picker, Content Fragment references, node ACLs.
What you give up: more setup, an AEM Cloud instance to run, and content that is now coupled to JCR rather than a portable document store.
Who pays: AEM Cloud entitlement and EDS. This is where the "paying twice" question gets loudest — section 10 answers it.
This is the approach the question is really about: keep AEM as the content backend, expose the content as JSON, and consume it in an EDS block.
AEM produces JSON in two common ways:
<path>.model.json..model.json on a content page.The Sling Model:
@Model(
adaptables = Resource.class,
resourceType = "myco/components/promo",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json")
public class PromoModel {
@ValueMapValue private String title;
@ValueMapValue private String description;
@ValueMapValue private String ctaLabel;
@ValueMapValue private String ctaHref;
public String getTitle() { return title; }
public String getDescription() { return description; }
public String getCtaLabel() { return ctaLabel; }
public String getCtaHref() { return ctaHref; }
}
AEM then serves, at /content/myco/promos/spring.model.json:
{
"title": "Spring Sale",
"description": "Up to 40% off selected lines.",
"ctaLabel": "Shop now",
"ctaHref": "/campaigns/spring"
}
The EDS block fetches and renders it:
export default async function decorate(block) {
const endpoint = block.querySelector('a')?.href
|| block.textContent.trim();
block.textContent = '';
block.classList.add('is-loading');
try {
const res = await fetch(endpoint, { headers: { accept: 'application/json' } });
if (!res.ok) throw new Error(`Promo request failed: ${res.status}`);
const promo = await res.json();
block.innerHTML = `
<h2>${promo.title}</h2>
<p>${promo.description}</p>
<a class="button" href="${promo.ctaHref}">${promo.ctaLabel}</a>`;
} catch (err) {
block.classList.add('has-error');
// eslint-disable-next-line no-console
console.error(err);
} finally {
block.classList.remove('is-loading');
}
}
The two things this approach must solve:
Access-Control-Allow-Origin for the EDS
domain, or the JSON must be served from the same domain (see Approach F)..model.json hit is an origin request to AEM Publish. Put it
behind the CDN with a sensible TTL, or you lose the EDS performance benefit by
round-tripping to AEM on every render.Where it fits: the content model already lives in AEM, authors will not move, but you want EDS to render the experience.
What you give up: you are back to running and paying for AEM Publish, and each JSON call is an origin dependency with its own failure and latency profile.
A more structured version of Approach C. Instead of a component's Sling Model, the content is a Content Fragment built on a CF Model (typed fields, variations, versioning), and AEM exposes it through GraphQL.
You define a persisted query in AEM (so the browser calls a stable, cacheable URL rather than sending a raw query):
# persisted query: promoByPath
query($path: String!) {
promoByPath(_path: $path) {
item {
title
description
cta { label href }
}
}
}
The EDS block calls the persisted-query endpoint:
export default async function decorate(block) {
const path = block.textContent.trim();
block.textContent = '';
const url = `https://author-or-publish.adobeaemcloud.com`
+ `/graphql/execute.json/myco/promoByPath;path=${encodeURIComponent(path)}`;
const res = await fetch(url, { headers: { accept: 'application/json' } });
const { data } = await res.json();
const promo = data.promoByPath.item;
block.innerHTML = `
<h2>${promo.title}</h2>
<p>${promo.description}</p>
<a class="button" href="${promo.cta.href}">${promo.cta.label}</a>`;
}
Why persisted queries matter here: they are the difference between a cacheable GET (good for EDS) and an ad-hoc POST query (an origin hit every time, and a larger attack surface). Always use persisted queries from browser code.
Where it fits: genuinely structured, reusable content — product data, author bios, location records — that is referenced from many pages and needs a schema.
What you give up: the full AEM stack to author and serve Content Fragments, plus the same CORS/caching work as Approach C.
Content lives in a headless CMS (Contentful, Hygraph, Sanity, etc.). The EDS block
fetch()es it exactly like Approach C or D — the mechanics are identical, only the
origin changes. No AEM at all.
Where it fits: you want structured content and headless authoring but do not have (or do not want) AEM. This is the "EDS without AEM, but still structured" option.
What you give up: the AEM ecosystem — and you now pay for a separate CMS instead.
The pattern most large enterprises converge on. EDS and traditional AEM live on one domain, and the CDN routes by path:
www.company.com/ and /campaigns/* → EDS (marketing, fast, edge-cached)www.company.com/app/* → traditional AEM (authenticated application)www.company.com/forms/* → AEM Formswww.company.com/api/* → AEM JSON endpoints (Approach C/D), same-origin so
no CORS problemThis is powerful because it combines the approaches instead of choosing one. The marketing site gets EDS performance; the application keeps AEM; and because it is one domain, EDS blocks can call AEM JSON endpoints same-origin, which quietly removes the biggest headache of Approaches C and D.
Where it fits: enterprises with an existing AEM investment and a marketing site that needs to be fast.
Who pays: both — but see the justification below, because here the double spend is usually the correct decision.
On the ADC POC the target was an editorial/marketing experience with authorable blocks, plus one form that had to reach a secured enterprise API. We used a combination rather than a single approach:
component-models.json / _page.json machinery in earlier chapters exists./bin servlet to hold it.We deliberately did not reach for Approach C/D (AEM JSON backend) for the
general content, because the content was authored, not structured-and-shared, and
adding a .model.json origin dependency to every block would have traded away the
EDS performance we were there to prove.
The lesson: the approaches are not mutually exclusive. Pick per surface. Author content through the surface that owns it; call an API only where you genuinely need runtime data.
Here is the blunt question answered directly.
If you already pay for AEM, adding EDS is justified when EDS buys you something AEM cannot easily give you on the same money:
Performance as a structural guarantee. AEM can hit good Core Web Vitals, but it takes sustained effort — caching, clientlib discipline, Dispatcher tuning. EDS starts at a near-perfect Lighthouse score by construction. If Core Web Vitals are a ranking and revenue lever for you, that structural advantage is the return on the extra spend.
Authoring velocity for marketing. DA authoring (Approach A) or EDS-delivered authoring lets a marketing team ship pages without a deployment pipeline. The saving is not the license — it is the reduced dependency on developer time for routine content.
Developer velocity. git push to deploy a block, no full AEM build/pipeline
for front-end changes. Faster iteration on the customer-facing layer.
You keep the AEM investment. Approaches B, C, D, and F all preserve AEM as the content system. You are not throwing AEM away — you are adding a faster delivery tier in front of the content you already manage. The spend is incremental, not duplicated, because the two systems do different jobs: AEM manages content, EDS delivers it.
The one-line version: you are not paying twice for the same thing. You are paying AEM to manage content and EDS to deliver it fast. The double spend is justified exactly when delivery performance and authoring/dev velocity are worth more than the incremental EDS cost — which, for a high-traffic marketing surface, they usually are.
Adding EDS is not justified, and you should stay on plain AEM, when:
If two or more of these are true for the whole site, EDS is the wrong spend. If they are true only for one section, use Approach F and let each section run on the technology that fits it.
Walk this top to bottom and stop at the first match:
With the architecture choice made, the rest of the series returns to building. The next chapter, CSS Architecture in EDS, covers how EDS structures styling without BEM, how block CSS is scoped, and the conventions that keep a growing block library maintainable.
Get an email when I publish the next chapter. No spam — just new technical deep-dives.
Comments
Share feedback or questions about this blog post.
No comments yet. Be the first to share your thoughts.