Edge Delivery Services · 14 of 29
EDS limitations and alternatives. Covers server-side logic, workflow, JCR queries, AEM Forms, MSM, Content Fragments, DAM schemas, and Dispatcher rules.
This chapter covers:
This chapter is for architects and senior developers.
The biggest risk in any EDS project is discovering a hard limitation after the project has started. This chapter is designed to surface those limitations before the project starts.
I want to be direct: some of these limitations are temporary tooling gaps that Adobe will close. Some are fundamental architectural constraints that will not change. I will distinguish between them.
For each limitation, I rate it on two dimensions:
Severity: How often does this matter in real projects?
Permanence: Is this a fundamental architectural constraint or a tooling gap?
Severity: 🔴 High | Permanence: 🏗️ Fundamental
You cannot run Java code, OSGi services, Groovy scripts, or any server-side computation during the page render cycle. There is no Sling resolution, no component rendering chain, no servlet that runs before the HTML is delivered to the browser.
Specific capabilities lost:
EDS delivers pre-built HTML from a CDN edge cache. CDN edge nodes do not run Java application servers. This is a fundamental architectural choice — it is why EDS achieves its performance guarantees. Allowing server-side computation would require an origin hit on every request, eliminating the performance model.
Option 1: External API pattern
Move server-side logic to an external microservice (AWS Lambda, Azure Function, Node.js server). Your block JS calls this API:
export default async function decorate(block) {
const productId = block.querySelector('div div').textContent.trim();
// External API handles all server-side logic
const response = await fetch(`https://api.company.com/products/${productId}`);
const product = await response.json();
block.innerHTML = '';
// Render product data
}
Security consideration: Be careful what you expose via client-side API calls. Rate limiting, authentication, and CORS configuration are now your responsibility on the API server.
Option 2: Pre-compute to JSON sheets
If the data is not real-time, pre-compute it to a Google Sheets or SharePoint spreadsheet. EDS exposes this as /data/products.json. Your block fetches it.
Option 3: xwalk path (if data is in AEM)
If the data is in AEM JCR and you are using Universal Editor, a Sling Servlet on the AEM author/publish instance can provide the data via a JSON endpoint that your block calls.
Severity: 🔴 High | Permanence: 🔧 Tooling (partially)
There is no native multi-step workflow in pure EDS:
In regulated industries (healthcare, financial services, pharma), content must pass through multiple reviewers before publishing. The inability to enforce this workflow is a compliance risk.
Option 1: xwalk + AEM Workflow (strongest option)
Since xwalk content lives in AEM JCR, standard AEM workflows are available. A workflow that requires legal review before the bin/franklin.delivery servlet makes content available to the EDS edge can enforce the approval chain:
This requires custom Sidekick plugin development to intercept the Preview action and verify workflow state before allowing it.
Option 2: Branch-based review (for DA authoring)
Authors work on a feature branch. A PR is opened for review. Reviewers comment and approve in GitHub. Merge to main triggers the preview pipeline.
This is developer-familiar but not author-friendly for non-technical teams.
Option 3: External workflow tool + Sidekick plugin
Integrate with Workfront, Jira, or a custom approval API. A custom Sidekick plugin checks the external system before allowing Preview/Publish:
// custom-sidekick-plugin.js
export async function onPreview(event) {
const pageUrl = window.location.href;
const approvalStatus = await fetch(`https://approval-api.company.com/status?page=${pageUrl}`);
const { approved } = await approvalStatus.json();
if (!approved) {
event.preventDefault();
showMessage('This page requires approval before preview.');
}
}
Severity: 🔴 High | Permanence: 🏗️ Fundamental
You cannot run JCR queries (XPath, SQL2, QueryBuilder) against the content repository:
The Query API provides a pre-built JSON index of page metadata. It is powerful for listing pages but has limits:
| JCR Query Capability | Query API Capability |
|---|---|
| Query by any JCR property | Query only indexed metadata fields |
| Real-time results | Updated only on page publish |
| Deep traversal (child pages N levels) | Flat index only |
| Query assets in DAM | Not available |
| Complex boolean conditions | Client-side JS filter |
| Result count / pagination at query level | Client-side slice |
| Faceted search | Not supported natively |
| Full-text search (across all node content) | Title/description only |
For content listings (blog index, news archive, product catalog): Query API + client-side filter is usually sufficient. Index the fields you need to filter on.
For complex search: Integrate with Adobe Search & Promote, Algolia, or an external search service. Index content into the search service on publish. Your search block calls the search API.
// blocks/search/search.js
export default async function decorate(block) {
const input = document.createElement('input');
block.append(input);
input.addEventListener('input', async (e) => {
const query = e.target.value;
if (query.length < 3) return;
// External search service
const results = await fetch(`https://search.algolia.io/query=${query}`);
renderResults(await results.json());
});
}
For DAM asset queries: Use AEM Assets API (available via REST from your block JS, with authentication handled client-side via Adobe IMS).
Severity: 🟡 Medium | Permanence: 🔧 Tooling
AEM Forms (Adaptive Forms) is not available in EDS:
Option 1: Simple HTML forms with external submission
For contact forms, registration forms, newsletter signups:
// blocks/contact-form/contact-form.js
export default function decorate(block) {
const form = document.createElement('form');
form.innerHTML = `
<input type="text" name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message"></textarea>
<button type="submit">Submit</button>
`;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(form));
await fetch('https://submit.company.com/contact', {
method: 'POST',
body: JSON.stringify(data),
});
});
block.append(form);
}
Option 2: Forms as a Block (Franklin-native approach)
The EDS boilerplate includes a forms approach using JSON-defined form schemas authored as a spreadsheet. This is less powerful than Adaptive Forms but authoring-friendly.
Option 3: Adobe Workfront Forms / third-party
For complex enterprise forms with conditional logic, multi-step flows, and integrations: use Formstack, Typeform, or a custom form service.
Option 4: Headless AEM Forms (Edge Delivery for Forms)
Adobe is investing in EDS-compatible Adaptive Forms rendering. This is an emerging capability (as of 2026) — check the Adobe roadmap.
Severity: 🔴 High | Permanence: 🏗️ Fundamental
AEM MSM is not available in EDS:
MSM is used for:
Option 1: Folder structure as content hierarchy
Multiple locales under shared folder structure, with page-level duplication:
/en/ ← English master
/fr/ ← French copy (manually maintained)
/de/ ← German copy (manually maintained)
This is MSM without the sync. Changes to the English master must be manually propagated to each locale. For small sites (under 100 pages), this is manageable. For large sites, it becomes a maintenance problem.
Option 2: Shared blocks + locale-specific content
Structure your site so the EDS blocks (code + layout) are shared but content pages are locale-specific. The developer work is the same across locales; only content differs.
Option 3: xwalk with AEM MSM
Since xwalk content is in AEM JCR, AEM MSM works with xwalk content. You get live copy relationships, rollout, and language masters — plus EDS delivery performance. This is the best option for complex multi-site requirements.
Architect recommendation: For projects with 5+ locales and significant shared content, the xwalk path with AEM MSM is strongly recommended. Pure DA authoring cannot replicate MSM.
Severity: 🟡 Medium | Permanence: 🔧 Tooling
EDS does not natively support AEM Content Fragments:
EDS has a native Fragment block for Experience Fragments:
| Fragment |
|-----------------------------|
| /content/experience-fragments/shared/nav-cta |
The Fragment block fetches the fragment URL, inlines its HTML into the current page. This works for visual component reuse but not for structured data reuse.
Option 1: JSON sheets for structured data
Define a spreadsheet schema for your content type. Authors fill in the spreadsheet. The spreadsheet becomes a JSON API:
Product Catalog spreadsheet:
| name | price | category | description |
|---------|--------|----------|-----------------|
| Item A | $99 | tools | Item description|
Available at: /data/products.json
This is sufficient for simple structured content (pricing tables, team members, office locations, FAQ items).
Option 2: External Headless CMS + EDS
For truly complex structured content (rich schemas, nested models, complex relationships), use a headless CMS alongside EDS:
Your EDS block fetches from the headless API:
export default async function decorate(block) {
const endpoint = block.querySelector('div div a')?.href;
const data = await fetch(endpoint).then(r => r.json());
// Render structured content
}
Severity: 🟡 Medium | Permanence: 🔧 Tooling (partially)
Full AEM DAM features are not available in EDS (DA authoring):
Severity: 🟡 Medium | Permanence: 🏗️ Fundamental
AEM's JCR ACL system provides component-level, page-level, and path-level access control:
EDS does not replicate this:
For xwalk: Since content is in JCR, standard AEM ACLs apply at the page level. Author permissions can be set per content path.
For DA: Access control is at the document/folder level in da.live. Granular component-level restrictions are not available.
Severity: 🟡 Medium | Permanence: 🏗️ Fundamental
RewriteRule)Custom URL rewriting: EDS supports a redirects.xlsx spreadsheet that defines URL redirects. For /old-path → /new-path redirections, this is sufficient. For complex RewriteRule logic (regex with conditions), a CDN layer (Fastly, Cloudflare) in front of EDS is required.
Authentication-based content: Handle authentication client-side. Protected content should not be in the EDS page itself — fetch it from an authenticated API after the user logs in.
Custom caching: EDS CDN cache behavior is managed by Adobe and is intentionally simple (cache everything, invalidate on publish). Complex per-URL caching strategies require a custom CDN.
Severity: 🟢 Low | Permanence: 🏗️ Fundamental
Client-side gating with an authentication service (Adobe IMS, Auth0, Okta):
// scripts/scripts.js (modified for gated sites)
const user = await checkAuth(); // Adobe IMS or similar
if (!user && isGatedPage()) {
window.location.href = '/login';
}
The HTML is delivered to everyone, but client-side JS redirects unauthenticated users. For genuinely secure content (not just gated), do not put it in EDS page HTML — it should come from an authenticated API call after login.
Use this framework when evaluating whether a project should use EDS:
✅ Primarily editorial content (articles, landing pages, campaign pages)
✅ Authors are non-technical (DA authoring model)
✅ Performance is a primary requirement (Core Web Vitals, LHS 100)
✅ Content volume is high but structure is low (many pages, simple blocks)
✅ No multi-step approval requirement (or xwalk is acceptable)
✅ No complex server-side rendering needed
✅ No AEM Forms requirement
✅ Small component library (< 20 blocks)
✅ Developer velocity is prioritized
✅ Marketing team manages site content independently
✅ Needs EDS performance AND AEM capabilities
✅ Complex approval workflows required
✅ MSM/multi-site with content inheritance required
✅ Large existing AEM component library (gradual migration)
✅ AEM Assets DAM integration required
✅ Existing AEM investment should be preserved
⚠️ More complex setup (requires AEM Cloud instance)
⚠️ Content lives in JCR (not pure EDS content model)
❌ Application is primarily transactional (not editorial)
❌ Complex server-side personalization is required
❌ AEM Forms is central to the experience
❌ Deep JCR query requirements (search, complex listings)
❌ Per-component access control is a compliance requirement
❌ Content model is deeply hierarchical with complex inheritance
❌ Large regulated content library requiring strict workflow enforcement
❌ Team has deep AEM expertise and no JavaScript frontend skills
Many enterprise projects use EDS and traditional AEM on the same domain:
www.company.com/ → EDS (marketing pages)
www.company.com/products → EDS (product catalog, editorial)
www.company.com/app/ → Traditional AEM (authenticated application)
www.company.com/forms/ → Traditional AEM Forms
www.company.com/search → External search (Algolia/SearchPromote)
The CDN layer routes requests to the appropriate backend based on path. This allows each section of the site to use the technology best suited to its requirements.
This is the pattern most large enterprises will converge on: EDS for editorial speed and performance, traditional AEM for complex application functionality.
| Capability | EDS | AEM | Use What? |
|---|---|---|---|
| Editorial page authoring | ✅ Excellent | ✅ Good | EDS |
| Component development | ✅ JS/CSS only | ✅ Full stack | Depends on need |
| Performance / Core Web Vitals | ✅ Structural 100 | ⚠️ Requires effort | EDS |
| Multi-step approval workflow | ❌ Not native | ✅ Full engine | AEM (or xwalk) |
| Component inheritance | ❌ None | ✅ Sling Resource Merger | AEM |
| JCR / complex queries | ❌ None | ✅ Full QueryBuilder | AEM |
| AEM Forms | ❌ Not available | ✅ Adaptive Forms | AEM |
| MSM / Live Copy | ❌ None (DA) / ✅ (xwalk) | ✅ Full MSM | AEM or xwalk |
| Editable template policies | ❌ None | ✅ Full | AEM |
| Content Fragments (structured) | ❌ Not native | ✅ Full CF + GraphQL | AEM |
| Server-side logic | ❌ None | ✅ Sling/OSGi | AEM |
| Dispatcher custom rules | ❌ None | ✅ Apache rules | AEM (or CDN) |
| Experimentation / A/B | ✅ Built in | ⚠️ Requires Target | EDS |
| Deployment speed | ✅ git push | ⚠️ Pipeline required | EDS |
| Authoring simplicity | ✅ Document authoring | ⚠️ Training required | EDS |
| DAM at scale | ❌ Basic (DA) / ✅ (xwalk) | ✅ Full DAM | AEM or xwalk |
| Multi-tenant single repo | ⚠️ CSS themes only | ✅ Overlay system | AEM |
| Scalability | ✅ CDN auto-scales | ⚠️ Requires infra | EDS |
| Total cost (editorial site) | ✅ Lower | ⚠️ Higher | EDS |
| Total cost (application site) | ⚠️ Higher (external APIs) | ✅ Self-contained | AEM |
This chapter framed when EDS is and is not the right choice. The next chapter, Ways to Use EDS — Architecture Approaches and the Cost Justification, turns that into how: the six architectural approaches for wiring EDS to content (pure DA, Universal Editor on AEM, AEM as a headless JSON backend, Content Fragments with GraphQL, external headless, and the hybrid same-domain pattern), the approach we chose on the POC, and a direct answer to the budget question — why run EDS when you already pay for AEM, and when adding it is a waste.
The code examples throughout this series come from a real production EDS block
library built on aem-boilerplate-xwalk. Every bug in the debugging chapter was a
real bug. Every architectural constraint in this chapter was encountered on a real
project. The best way to learn EDS is to build something — use this series as your
reference while you build.
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.