Two ways to author content, and one pipeline that delivers it. Content and code ship on separate tracks, so neither one waits on the other. And because every publish comes out as plain semantic HTML, a browser, a mobile app, and a screenless device can all read from the same source.
Crosswalk (the Universal Editor on AEMaaCS) and Experience Workspace (da.live) only differ in where the content lives and how it's mounted. From the Admin Service onward, they're the same system.
Authors publish through the Admin Service, and developers ship through Code Sync. Neither one waits on the other, and that's really the heart of how EDS works.
Every branch resolves to its own preview at <branch>--<repo>--<owner>.aem.page and its published version at .aem.live. Your production domain sits in front of .aem.live through your CDN.
GET /page → <div class="hero">…</div>decorate()scripts.js enhances each block in placeExplore the full interactive board for more detail: wovea.live board ↗
The two paths only differ in where the content lives and how the repo mounts it. Once you get past the Admin Service, they're the same system.
This is in-context, component-based authoring. The content lives in the JCR on AEM as a Cloud Service, and the Universal Editor lets you edit the rendered page directly, mapping each component to an EDS block.
mount → /bin/franklin.delivery/<owner>/<repo>/<ref>/*
Adobe's EDS-native document CMS. You edit documents right in the browser, with versioning built in, and you don't have to mount Google Drive or SharePoint to do it.
mount → content.da.live/<org>/<site>
This holds the site code — blocks, scripts, and styles — along with fstab.yaml (the mount points), paths.json (which maps AEM paths to URLs), and the component definitions and models that drive the Universal Editor.
adobe/aem-boilerplate · Vanilla JS · CSS
fstab.yaml decides which mount a site uses, and from there, everything downstream is identical.Component is a familiar idea in front-end development: small pieces of functionality that you nest inside bigger ones. A block takes a flatter approach.
You compose by nesting. A button and an image go inside a teaser, and a teaser goes inside a carousel. That's great for reuse, but every layer adds another wrapper, and a framework has to assemble and hydrate the whole tree in the browser. What you end up shipping is deep, heavy markup — the thing people call “div soup.”
<Carousel>
<Teaser>
<Button>Shop</Button>
<Image src="hero.jpg" />
</Teaser>
</Carousel>
A block flattens that tree into a single unit, and blocks don't nest: a section holds blocks, and a block holds content, but a block never holds another block. It arrives as semantic HTML and gets enhanced by a decorate() function in the browser — plain JavaScript, no framework. You give up some of the encapsulation a component gives you, and in return the page stays flat and fast.
<div class="carousel block"> <picture><img …></picture> <h2>…</h2> <a class="button">…</a> </div>
When you preview, it pulls from the content source, normalizes it, and writes to the Content and Media Buses (.aem.page).
When you publish, it promotes that content to the delivery tier (.aem.live) and purges the CDN.
admin.hlx.page API · Adobe-managed
Normalized page content in both preview (.aem.page) and published (.aem.live) states, per branch.
Content-addressable image & video storage (media_<hash> filenames), so identical assets are stored and cached once.
JS, CSS, and block code synced from GitHub and versioned per branch. It deploys independently of the content.
Edge compute that composes content from the Content, Media, and Code Buses into the final response. It serves both <branch>--<repo>--<owner>.aem.page and .aem.live.
Separate pipelines turn the bus content into semantic HTML, query-index JSON, and optimized media. This is also where head.html, metadata, and redirects get applied.
A customer- or Adobe-managed CDN (Akamai, Fastly, Cloudflare, or CloudFront) fronts the production domain and handles DNS and TLS. It holds the cached copy the public actually hits.
Publish and code-sync events emit targeted purges, so only the changed paths and their dependents get invalidated. There's never a full flush.
.aem.page and a published version at .aem.live. Your production domain sits in front of .aem.live through the CDN, and the purge queue keeps that cache in sync for you.The same Content Bus entry is addressable three different ways, and the author never has to pick one. Whatever's consuming it — a browser, a native app, a device — just asks for the shape it can use.
{
"metadata": {
"title": { "text": "Exploring AmmoVault…" },
"canonical": { "href": "…/ammovault-ammunition-reserve" }
},
"content": [
{ "section": [ { "type": "block",
"name": "spotlight", "content": […] } ] }
]
}
live example ↗
{
"metadata": { "schemaName": "doctors-schema" },
"data": {
"name": "Tahmina Aafreen, MD",
"primarySpecialty": "Obstetrics and Gynecology",
"languages": ["English","Hindi","Urdu"],
"acceptingNewPatients": true
}
}
live example ↗
Here's the key idea: all content is structure. Semantic HTML is structured content too, just the same authored source expressed as tags. So no consumer is decorating a blob; each one takes the shape it can read, and the structure carries through.
It gets semantic HTML right away, then scripts.js decorates the DOM and lazy-loads the blocks. Core Web Vitals is the target the whole architecture is built around.
A native shell hosts the web component for authored screens like promos, help, legal, and campaign content, and pulls its lists, nav, and config from JSON. You can ship content without an app-store release.
A custom element fetches a .plain.html fragment and runs the same block-decoration code from the Code Bus. That lets you render authored content anywhere, with no EDS page around it.
A kiosk, digital sign, wearable, or voice surface with no browser engine. It polls the JSON endpoints and renders natively, from the same authored source, with no dependency on markup.
.plain.html, and JSON are just three encodings of one structured source, and semantic HTML is structure, not styling. That's why a browser, a native app, and a screenless device can all consume the same publish without needing a separate content model.You don't tune a high score in after the fact. It falls out of how the page is built and served: no framework runtime, semantic HTML from the edge, and a disciplined three-phase load sequence.
Semantic HTML comes straight from the CDN edge, so there's no server render and a low TTFB. The LCP image is prioritized and delivered as a responsive <picture> in modern formats from the Media Bus.
Explicit width/height on every image, reserved block space, and a controlled font strategy mean the layout never reflows as it loads.
It's vanilla JS with no hydration. Blocks decorate progressively in small steps on the main thread, and third-party weight is held back to the delayed phase.
The same flattening that makes a page fast also makes it legible to machines. But the stakes are different for search engines than they are for AI answer engines.
Deep component trees usually still get indexed, so the cost here is indirect, through speed. Core Web Vitals is a confirmed ranking signal, and Lighthouse penalizes a DOM over ~800 nodes (warning) or ~1,400 (error), or a nesting depth over 32 — which is exactly what component nesting produces.
Flat HTML is faster on INP and LCP, and it's guaranteed to be indexable because there's no render step to fail.
Across 500M+ GPTBot fetches, not one executed JavaScript (Vercel / MERJ). GPTBot, ClaudeBot, and PerplexityBot only read the initial HTML, so a page that ranks #1 on Google can be invisible to ChatGPT if it's client-rendered.
A block's semantic HTML is in the response by default, and the same content is served as JSON on top of that.
Sources: Vercel / MERJ AI-crawler analysis · Lighthouse DOM-size audit · Google Search Central — Page Experience
This is the code track, start to finish. Authors publish on their own separate track through the Admin Service, so a deploy never blocks a publish, and a publish never waits on a deploy.
aem up serves your working tree and proxies everything else from the preview tier. There's nothing to install beyond the CLI: no seed data, no database, and no drift between machines.
Push a branch and it resolves to its own preview URL seconds later, running that branch's code against the same shared content. You review on a real URL, not a screenshot.
It runs independently of authoring, so a deploy never blocks a publish. And rolling back is just a git revert.
Actions gates the merge, while Code Sync just copies files and gates nothing. They run in parallel, so branch protection on main is what really gates a release.
Walk the full interactive board for more detail: wovea.live board ↗
It starts a proxy that serves code from your working tree and pulls content from the remote preview, and it live-reloads on save.
npx @adobe/aem-cli
Just a git clone: blocks/, scripts.js, styles.css, head.html, fstab.yaml. No build step, no bundler, no node_modules in what gets served. The files you edit are the files that ship.
In EDS a branch isn't a step toward an environment; the branch is the environment. Push feature/hero-block and it becomes a full site.
You get feature-hero--repo--owner.aem.page: a full, shareable environment running that branch's code against the same shared content, with no per-branch copy.
People review against the preview URL itself, so the preview URL is the review artifact, gated by the required status checks.
This runs on GitHub-hosted runners, on push and pull_request: ESLint and Stylelint, unit tests, and a Lighthouse/PSI budget against the branch preview URL. It reports back as required status checks.
It's defined in .github/workflows, so your CI is code too, versioned in the same repo and reviewed in the same PR.
You install it once on the repo. It watches every branch, not just main, and pushes each commit to the Code Bus within seconds. There's no build, no bundling, and no test step.
It runs in parallel with CI, not after it, and it gates nothing.
main is the gate that actually matters.Merging to main is the release. There's no build, no deploy job, and no maintenance window. Code Sync picks up main, emits a targeted purge, and the change is live at …--owner.aem.live behind the CDN in seconds. To roll back, you git revert.
stageThis is a permanent branch that feature branches merge into first, before they promote to main. It earns its keep in exactly one case: because fstab.yaml is per-branch, stage can mount a different content source, which is the only real content isolation EDS gives you. The cost is drift and batched merges.
| Component | Type | Owner | Role |
|---|---|---|---|
| AEM Sites + Universal Editor | Service | Authoring — structured | In-context component authoring; content in JCR on AEMaaCS. |
| Experience Workspace — da.live | Service | Authoring — document | EDS-native document CMS with versioning. |
| GitHub Repository | Service | Development | Blocks, styles, fstab.yaml, paths.json, UE component models. |
| Sidekick / Publish Trigger | Service | Authoring | Fires Preview and Publish from UE or da.live. |
| AEM Admin Service | Service | Adobe-managed | Orchestrator: normalize → Content/Media Bus → promote → purge. |
| AEM Code Sync | Service | Adobe-managed | GitHub App syncing merged code to the Code Bus. |
| Content Bus | Datastore | Adobe-managed | Normalized page content, preview + published, per branch. |
| Media Bus | Datastore | Adobe-managed | Content-addressable media (media_<hash>). |
| Code Bus | Datastore | Adobe-managed | JS/CSS/block code from GitHub, versioned per branch. |
| Experience Composition Service | Service | Adobe-managed | Edge compute composing buses into the final response. |
| Rendering Pipelines | Service | Adobe-managed | HTML / JSON / media pipelines; head.html, metadata, redirects. |
| Production CDN | Service | Customer infrastructure | Akamai/Fastly/Cloudflare/CloudFront; DNS, TLS, cache. |
| Cache Purge / Invalidation | Queue | Adobe-managed | Targeted purge of changed paths + dependents. |
| Browser · Mobile App · Web Component · IoT | Service | End user / Mobile / Device | Consumers of HTML, .plain.html fragments and JSON. |
| RUM / Operational Telemetry | Service | Adobe-managed | Sampled real-user monitoring; feedback into code + content. |
QWhy is this faster? What's actually different about performance?
The page ships as semantic HTML straight from the CDN edge, with no server render and no framework to boot up, so TTFB and LCP stay low. Then scripts.js loads blocks in three phases — eager, lazy, delayed — which keeps the main thread free for good INP, and images carry fixed dimensions so nothing reflows and CLS stays near zero.
QDoes this reduce our licensing or infrastructure cost? depends on your setup
Often it does, but it depends on your contract. Delivery is Adobe-managed, so there's no publish or dispatcher fleet for you to run and patch. What it does to AEM licensing depends on what you keep: Crosswalk still uses AEMaaCS for authoring, while Experience Workspace doesn't.
QIs this secure? Are we more exposed without a publish/dispatcher layer? depends on your setup
It's a different model, not a weaker one. There's no running publish app or dispatcher for an attacker to reach, because delivery is static content from the edge, which is a much smaller attack surface than a live Java app. Whether your overall posture improves depends on what you're replacing.
QWhat's the security model at the origin, and who's responsible for what? confirm with Adobe
There's no OSGi/Sling runtime at the origin to patch — it serves pre-rendered content from the buses, not a live application. Adobe owns the edge and its patching; you own your block code, your content sources, and your CDN and WAF rules. Confirm the exact split with Adobe for your contract.
QHow do we integrate backend services, APIs, or commerce data without Sling/OSGi?
You integrate at the edge or in the browser instead of a Java layer. Blocks call your APIs client-side for dynamic data like pricing and inventory, and AEM Edge Functions can compose or guard requests server-side, with their own KV store. Read-heavy data can also come through structured JSON.
QCan existing AEM components or client libraries be reused, or is it all rebuilt as blocks?
Mostly rebuilt, and this is the honest one. Blocks are vanilla JS and CSS, not HTL/Java components or clientlibs, so existing components don't port directly. You can carry over design, content models, and a lot of CSS, but plan for a front-end rebuild, not a lift-and-shift.
QWhat does CI/CD look like — is Cloud Manager still in the picture?
For the EDS site it's GitHub-driven, not Cloud Manager. Code Sync deploys from your repo to the Code Bus on every push, and GitHub Actions gates the merge. If you're on Crosswalk you still have AEMaaCS for authoring, but the front-end code path doesn't go through Cloud Manager pipelines.
QHow do we monitor and debug production without a traditional Publish instance?
You lean on RUM and the repo, not a server console. Adobe's operational telemetry gives you real-user Core Web Vitals per release, and the Sidekick plus branch-preview URLs let you reproduce issues. Since delivery is static content, most problems trace back to content in a bus or code in the repo.
QHow do redirects, metadata, and SEO get managed without Dispatcher or HTL templates?
These move into content and config instead of templates. Redirects live in a spreadsheet in the repo, metadata comes from a metadata sheet and per-page values, and head.html sets the global head. The rendering pipeline applies all of it, so you manage SEO as content, not Dispatcher rules.
QIs our site AI/LLM-ready out of the box, or is there still work to do?
You start well ahead, but it's not zero work. Pages ship as semantic HTML in the first response and the same content is available as JSON, so AI crawlers can actually read you — which is where most sites fail. What's still on you is good heading structure, real metadata, and JSON-LD where you want rich answers.
QIs the semantic HTML optimized for AI search / GEO out of the box?
Largely yes, because of how it's built. The content is in the initial HTML, so crawlers that don't run JavaScript still see it, and it's also addressable as JSON for systems that prefer structured data. That's the biggest GEO lever, and you get it without extra work.
Crosswalk and Experience Workspace share one pipeline, so you pick the front door per site, not per architecture.
Content and code deploy separately, so neither release path blocks the other.
HTML, .plain.html, and JSON all come from one publish, so every channel is served without a rebuild.
It's all aimed at Core Web Vitals: semantic HTML from the edge, decorated on the client, measured by RUM.