Next.js on Edge Delivery Services
Edge Delivery stays the content source; React Server Components render the page at the edge.
This site keeps AEM Edge Delivery Services as the content and authoring source — consumed for decoupled rendering rather than as a true headless CMS — and replaces the client-side aem.js decoration with a Next.js App Router rendering layer built on React Server Components. Authors keep Document Authoring, the sidekick, and the preview/publish workflow exactly as before — the final HTML is produced by React components instead of by mutating the DOM in the browser.
0 KB
Client JavaScript for static blocks
The edge
Where rendering runs
Unchanged
Authoring workflow for editors
Architecture at a glance
Cloudflare fronts Edge Delivery. A Worker renders Next.js at the edge and caches the result; Edge Delivery stays the upstream content origin. Publishing purges the matching cache entries so the next request re-renders.
The big idea
The aem.js library does two separable jobs: it parses the delivered semantic HTML into sections and blocks, and then it decorates the DOM. This project keeps the parse step as pure server-side functions and replaces the decorate step with React component resolution. The block contract — sections, blocks, and cells — is already a component tree; we simply render it with React.
How a request flows
A single catch-all route renders every path through four steps:
- FetchPull the page's
.plain.htmlfrom the Edge Delivery origin and rewrite relative asset URLs to absolute ones. - ParseNormalize the HTML into a neutral tree of sections, blocks (name, variants, and a grid of cells), and default content.
- ResolveLook up each block name in a registry that maps it to a React component.
- RenderRender static blocks as Server Components with zero client JavaScript; hydrate interactive blocks as isolated islands.
Native decoration vs. this approach
Native Edge Delivery
- The browser downloads HTML and
aem.jsmutates the DOM in place. - A block is a
decorate(element)function. - Decoration runs in the browser.
- The Edge Delivery CDN serves and invalidates.
This project
- The server fetches HTML, parses it to data, and React renders fresh markup.
- A block is a React component that receives parsed cells.
- Rendering runs in a Cloudflare Worker at the edge.
- Cloudflare caches; Edge Delivery push invalidation purges by tag.
How blocks are organized
Each block lives in its own folder under /blocks/<name>/ next to its styles, and is reached through a thin entry shim:
Name.jsxThe React component — pure and dependency-free. It receives structured cells and emits its own JSX.
name.jsA two-line entry shim that imports the JSX, giving the registry a stable lowercase block name.
name.cssCo-located styles, shipped only when the block actually appears on a page.
Converted so far: hero, cards, and columns — this very page is rendered through all three.
What the Cloudflare Worker does
Edge Delivery hosting cannot run a server, so it cannot execute the React rendering. Instead, the entire Next.js app is compiled by OpenNext into a single Cloudflare Worker that runs at the edge, and Cloudflare sits in front of Edge Delivery as the content origin. The Worker is the application: it handles every incoming request, renders pages, and serves the cached result.
On a cache miss it fetches the page's .plain.html from the Edge Delivery origin, parses it, resolves each block to a React component, renders the HTML, and caches the response tagged with page:<slug>. When an author publishes, Edge Delivery push invalidation purges the matching tagged entries, so the next visit re-renders from fresh content. One tag scheme ties together the data-cache tag, the response cache tag, and the on-demand revalidation key.
Running it locally
The app requires Node 18 or newer. From the repository root:
nvm use 22 # or any Node >= 18
npm install
npm run dev # Next.js dev server at http://localhost:3000
By default the dev server pulls content from the Edge Delivery preview origin. Point it at a different branch or environment with an environment variable:
EDS_ORIGIN=https://main--next-eds--adobedevxsc.aem.page npm run dev
To exercise the real deployment path — the actual Cloudflare Worker running under Cloudflare's runtime (workerd) rather than the Node dev server — build and preview it locally:
npm run preview:cf # builds the Worker with OpenNext and runs it locally
npm run deploy:cf # builds and deploys the Worker to Cloudflare
Why this keeps performance high
Most content renders as React Server Components and ships no client JavaScript, matching the native Edge Delivery performance profile. Only interactive blocks become hydrated islands, so JavaScript cost is opt-in and proportional to what a page actually needs.
SEO and GEO impacts
Because the Worker returns fully server-rendered HTML — not a client-side app that hydrates in the browser — both traditional search crawlers and AI answer engines (GEO, generative engine optimization) receive complete, semantic markup on the first request. The approach keeps the discoverability profile of native Edge Delivery rather than trading it away for a single-page app.
Search engines (SEO)
- Complete HTML at first byte — no JavaScript required to see the content.
- Fast time-to-first-byte and strong Core Web Vitals from edge caching and near-zero client JavaScript.
- Server-rendered title, description, canonical, and structured data are fully indexable.
Answer engines (GEO)
- AI crawlers that do not execute JavaScript still receive the full, clean content.
- Semantic headings, lists, and blocks are easy for models to extract and cite.
- Cacheable, fast responses are friendly to crawl budgets.
Watch-outs
- Keep one canonical surface. The same content can be served by both the Edge Delivery origin and the Cloudflare front door. Point canonical URLs at one of them and keep the other out of the index, or the two surfaces compete as duplicate content.
- The Worker must emit head metadata. Title, description, canonical, Open Graph, and JSON-LD have to be rendered server-side from the Edge Delivery metadata — not injected by client JavaScript after load.
- Align robots, sitemap, and llms.txt to the canonical host so both search and AI crawlers discover the right URLs.