Next.js on Edge Delivery Services

Edge Delivery stays the content source; React Server Components render the page at the edge.

Read the architecture View source

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.

Request flow: the browser hits a Cloudflare Worker that fetches, parses, resolves and renders content from the Edge Delivery origin, caches it by the page tag, and is purged by push invalidation when an author publishes.

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:

  1. Fetch
    Pull the page's .plain.html from the Edge Delivery origin and rewrite relative asset URLs to absolute ones.
  2. Parse
    Normalize the HTML into a neutral tree of sections, blocks (name, variants, and a grid of cells), and default content.
  3. Resolve
    Look up each block name in a registry that maps it to a React component.
  4. Render
    Render 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.js mutates 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.jsx

    The React component — pure and dependency-free. It receives structured cells and emits its own JSX.

  • name.js

    A two-line entry shim that imports the JSX, giving the registry a stable lowercase block name.

  • name.css

    Co-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.