---
name: aem-edge-delivery-services
description: Build and review sites on AEM Edge Delivery Services (aem.live, formerly Franklin/Helix) — boilerplate project setup, content sources, blocks, loading phases, spreadsheets/JSON, preview/live and the Sidekick, with a 100 Lighthouse score as the bar. Use this whenever the user works in an aem-boilerplate repo, writes or debugs a block (blocks/<name>/<name>.js with export default decorate(block)), edits scripts.js, delayed.js, styles.css or fstab.yaml, configures mountpoints or the configuration service, runs aem up, uses .aem.page/.aem.live or branch preview URLs, builds query-index, redirects or headers sheets, adds third-party scripts, fights LCP or PageSpeed failures on a PR, designs content models for Google Drive, SharePoint, Document Authoring or Universal Editor authoring, or asks whether EDS fits versus traditional AEM Sites. Also apply it when someone wants to add React, a bundler or npm build to an EDS project.
metadata:
  technology: AEM
  type: development
---

# AEM Edge Delivery Services

Edge Delivery is a "no build, no framework" platform: content is authored as documents or in Universal Editor, turned into semantic HTML at the edge, and decorated by small vanilla JS/CSS blocks loaded in strict phases. Every decision is judged by two questions: does it keep the page at a 100 Lighthouse score, and can an author create it without a developer?

## 1. When EDS fits

| Situation | Choose | Why |
|---|---|---|
| Marketing/content site, performance and time-to-launch matter most | **EDS** with document authoring | Fastest pages, simplest authoring, no build pipeline |
| Same, but authors need AEM governance, MSM/workflows, structured components | **EDS + Universal Editor (AEM authoring)** | AEM author UX, EDS delivery |
| Deep integration with AEM components, personalization on AEM publish, complex OSGi logic | **Traditional AEM Sites** | Server-side Java/HTL model and existing component library |
| App-like, heavily stateful UI | Headless/SPA, possibly embedded in EDS pages | EDS blocks are for content, not apps |

## 2. Project setup and content sources

- Start from the **aem-boilerplate** GitHub template (use the Universal Editor variant of the boilerplate when authoring in AEM); install the **AEM Code Sync** GitHub app on the repo — it syncs code to the edge on every push.
- Content source: Google Drive or SharePoint documents, **Document Authoring (DA)**, or AEM authoring via Universal Editor. Classic projects declare it in `fstab.yaml`; newer projects can use the **configuration service** (including "repoless" multi-site setups). Check current aem.live docs for which applies to your project before editing either.

```yaml
# fstab.yaml (document-based projects)
mountpoints:
  /: https://drive.google.com/drive/folders/<folder-id>
```

- URLs: `https://main--<repo>--<owner>.aem.page` (preview) and `.aem.live` (live); every branch gets `https://<branch>--<repo>--<owner>.aem.page`. Keep branch names short and lowercase (they become hostnames).
- Authors use the **Sidekick** browser extension to Preview (→ `.page`), Publish (→ `.live`), and unpublish. Production CDN points at `.live`.

## 3. Page structure and content modelling

- A document becomes **sections** (split by `---` horizontal rules), each containing **default content** (headings, paragraphs, lists, images, links) and **blocks** (tables whose first row is the block name).
- `Section Metadata` table inside a section sets section classes/data (e.g. `Style | highlight`). A `Metadata` table at the end of the page sets `<meta>` (title, description, image, template, custom keys read via `getMetadata`).
- Block options in the header row become classes: `Columns (wide, dark)` → `<div class="columns wide dark">`.
- Model for authors: prefer default content over blocks, few rows/columns, no invisible magic cells, names in plain language. If a block needs a manual to author, redesign it.
- Reuse content with **fragments** (a `fragment` block referencing `/fragments/...`); header and footer are fragments loaded from `/nav` and `/footer`.

## 4. Writing blocks

```
blocks/cards/cards.js   → export default function decorate(block) { ... }
blocks/cards/cards.css  → all selectors scoped under .cards
```

```js
import { createOptimizedPicture } from '../../scripts/aem.js';

export default function decorate(block) {
  const ul = document.createElement('ul');
  [...block.children].forEach((row) => {
    const li = document.createElement('li');
    li.append(...row.children);
    [...li.children].forEach((col) => {
      col.className = col.querySelector('picture') ? 'cards-card-image' : 'cards-card-body';
    });
    ul.append(li);
  });
  ul.querySelectorAll('picture > img').forEach((img) => img.closest('picture')
    .replaceWith(createOptimizedPicture(img.src, img.alt, false, [{ width: '750' }])));
  block.replaceChildren(ul);
}
```

- Decorate the existing DOM; don't discard author content or rebuild it from hard-coded strings. Keep blocks small (a few hundred lines at most) and single-purpose; variants via classes, not new blocks.
- Use `aem.js` helpers (`createOptimizedPicture`, `loadCSS`, `loadScript`, `getMetadata`, `readBlockConfig`, `toClassName`, `decorateIcons`). **Never edit `scripts/aem.js`** — it is the shared library upgraded from upstream; put project logic in `scripts/scripts.js`.
- CSS: mobile-first, scoped to the block class, CSS custom properties from `styles/styles.css`; no global resets inside blocks.
- Async `decorate` is fine; `await` only what's needed before first paint.

## 5. Loading phases and performance

- **Eager** (`loadEager`): decorate the page and load the first section only, so the LCP element (usually the hero image or H1) renders with minimal JS/CSS. Nothing third-party here.
- **Lazy** (`loadLazy`): remaining sections and blocks, header/footer, `lazy-styles.css`, fonts.
- **Delayed** (`scripts/delayed.js`, a few seconds after load): analytics, tag managers, chat, consent, marketing pixels. This is where third-party scripts go.
- Target a **100 Lighthouse/PageSpeed score** on mobile. Protect LCP: don't lazy-load the first image, don't insert content above the fold after load, reserve space to avoid CLS, keep eager payload tiny (the boilerplate keeps pre-LCP payload in the ~100 KB range).
- **Auto-blocking** (`buildAutoBlocks` in `scripts.js`) creates blocks from content patterns (e.g. hero from a leading picture + H1, fragments from links) so authors don't have to — keep it deterministic and cheap.

## 6. Spreadsheets, JSON and site config

- Any sheet is served as JSON (`/data/offices.json` → `{ total, offset, limit, data: [...] }`, `?offset=&limit=`, multi-sheet via `?sheet=`). Use it for lists, lookups and block data instead of hard-coding.
- **query-index**: an index definition produces `/query-index.json` with page metadata for listings, search and sitemaps; filter client-side or build a per-section index for large sites.
- **Redirects** sheet for 301s; response **headers** and other site settings via the project configuration (older projects used config sheets, newer ones the configuration service) — verify the current mechanism in aem.live docs.
- Forms: use the current EDS forms approach (form block backed by a sheet, or AEM Forms for EDS) — confirm availability and submission handling before designing.

## 7. Development workflow

- Local: `npm i -g @adobe/aem-cli`, then `aem up` → `http://localhost:3000`, serving local code with content proxied from preview.
- `npm run lint` (ESLint airbnb-base + Stylelint) must pass; no build step, no transpile, ship ES modules as written.
- PR workflow: push a branch, test on `<branch>--<repo>--<owner>.aem.page`, include before/after test URLs in the PR. The Code Sync / PSI checks run PageSpeed on those URLs — a PR that drops the score is not mergeable in spirit even if the check is overridden.

## Deliverable format (block spec)

1. **Block name and purpose**, variants (class options).
2. **Authoring table**: rows/columns authors fill, with an example and what's optional.
3. **Resulting DOM** after decoration and the CSS/responsive behaviour.
4. **Data**: sheets/JSON/query-index used, with fields.
5. **Performance**: loading phase, LCP impact, images and third-party scripts.
6. **Test URLs** (branch preview) and lint/PSI results.

## Anti-patterns to reject

- Adding React/Vue, a bundler, TypeScript build or npm runtime dependencies to page delivery.
- Editing `scripts/aem.js`; forking boilerplate internals instead of using `scripts.js`.
- Third-party scripts in `head.html` or eager phase; anything that blocks or delays LCP.
- Huge multi-purpose blocks; blocks that ignore author content and hard-code copy.
- Author-hostile models: deep nested tables, positional magic, JSON in cells.
- Global CSS leaking from blocks; unscoped selectors.
- Content or config hard-coded in JS that belongs in a sheet or metadata.
- Merging PRs without branch test URLs or with a PageSpeed regression.
