Developer Demo

System Architecture

How Optimizely SaaS CMS, Graph, and this Next.js app connect - request flow, ISR caching, and cache invalidation on publish - plus how the managed SaaS CMS compares to self-run CMS 13.

What is a headless CMS? #

A CMS (Content Management System) is where editors write, organise, and publish content. "Headless" describes how it connects to your website. A CMS can be coupled (it renders the site itself), headless (it only serves content as data), or hybrid - one backend that does both. Optimizely CMS 13 enables that hybrid path.

Traditional (coupled)

Browser

GET /articles

Coupled CMS server

e.g. CMS 12 - .NET controller looks up content

Razor view (MVC)

renders the full HTML

Browser

shows the HTML page

Hybrid (CMS 13)

Browser

GET /page

CMS 13 backend

same content, two delivery modes

Coupled: Razor view (MVC)

CMS 13 renders the HTML itself

or

Headless: Optimizely Graph

frontend queries JSON, renders HTML

Headless (this demo)

Browser

GET /articles

Next.js app

owns the route - queries Optimizely Graph

Optimizely Graph

returns content as JSON

Next.js renders HTML

RSC, on ISR cache miss - then cached at the CDN

Where the HTML is rendered - in a coupled CMS the CMS renders the page; in a headless setup your frontend does; in a hybrid setup the CMS 13 backend can do both.

Traditional ("coupled") CMS - e.g. CMS 12 / CMS 13

One system handles both authoring and rendering. Optimizely CMS 12 and CMS 13 are examples of this model.

  • -Editors write content in the CMS admin.
  • -The CMS generates the HTML and sends it to visitors directly.
  • -Changing the look of the site means changing CMS themes or templates.
  • -Content is locked to one presentation - it can only appear on the site the CMS controls.

Hybrid CMS - Optimizely CMS 13

One backend does double duty. CMS 13 can render its own pages (the head) and expose the same content as data through Optimizely Graph - so each application decides which model it consumes.

  • -One .NET backend both renders pages (MVC / Razor) and serves content as data via Optimizely Graph.
  • -CMS 13 can run fully coupled like CMS 12, or deliver headless through Graph - the same delivery layer this demo uses.
  • -Editors keep a single CMS; each application decides whether to consume rendered pages or content as data.
  • -Enables incremental migration - keep the existing server-rendered site while a new headless app is built alongside it.

Headless CMS (this demo)

The CMS stores and manages content. A separate frontend (this Next.js app) fetches that content via API and decides how to display it.

  • -Editors write content in Optimizely CMS.
  • -Content is delivered as structured data via Optimizely Graph (a GraphQL API) - not as HTML.
  • -This Next.js app fetches that data, renders it, and sends HTML to visitors.
  • -The same content can power a website, a mobile app, and an email newsletter - all from one source.
  • -Developers and editors work independently - no code deployment is needed when content changes.

SaaS CMS vs CMS 13 - who maintains what #

SaaS CMS (what this demo runs) and CMS 13 are both Optimizely CMS with the same authoring experience. The choice between them is operational - who runs the backend, who owns versions and upgrades, and where the maintenance work lands.

Same editor experience

CMS 13 and SaaS CMS share the same authoring UI and content modelling, so editors see no difference between them. Everything below is about operations, not authoring.

Hosting & backend ops
SaaS CMSFully managed by Optimizely - no servers, runtime, or database for you to run.
CMS 13You host and operate it (PaaS / DXP or self-managed) - infrastructure, runtime, and database are yours.
Version management
SaaS CMSEvergreen - there is no version to track; everyone is always on the current release.
CMS 13Pinned to a CMS 13.x version; you track packages and decide when to move.
Upgrades & patching
SaaS CMSAutomatic and continuous - features and fixes ship with no upgrade projects.
CMS 13You plan, test, and run upgrades and security patches on your own schedule.
Scaling & availability
SaaS CMSOptimizely scales the backend and Graph; delivery scales at your hosting CDN.
CMS 13You size, scale, and plan availability of the backend infrastructure.
What you build & maintain instead
SaaS CMSThe whole delivery side - the Next.js frontend, hosting / CDN, Graph queries, ISR caching, publish webhooks, and search and other integrations.
CMS 13Less delivery plumbing if you use the coupled head - MVC / Razor renders pages directly; stand up a separate headless app alongside it only when a channel needs one.
Extensibility & built-in features
SaaS CMSA managed, closed runtime with a fixed feature surface - you extend it through APIs, webhooks, apps, and your frontend, not by adding backend code. Capabilities beyond what it ships become extra dev you build and maintain outside the CMS.
CMS 13Full .NET extensibility - custom editor UI, server-side logic, scheduled jobs, content providers, add-ons, and in-process integrations all run inside the app. More is built in and more can be added, but that code is yours to maintain through upgrades.
Rendering model
SaaS CMSHeadless only - a separate frontend always renders.
CMS 13Coupled, headless, or hybrid - render in the CMS, via a frontend, or both.
Best fit
SaaS CMSTeams wanting zero backend maintenance and ready to own a modern delivery stack (this demo).
CMS 13Teams wanting control of the backend and version lifecycle, or needing coupled rendering alongside headless (incremental migration).

Where the SaaS feature gap shows up

CMS 13 can absorb these inside the .NET app; on SaaS CMS they usually become build-and-maintain work in your own services or frontend, because they cannot run inside the managed CMS:

  • -Server-side personalization and visitor targeting (on SaaS these run as separate experimentation / personalization services plus your frontend).
  • -Custom editor UI - bespoke property editors, admin plugins, and dashboard widgets.
  • -Custom server-side logic - publish-pipeline event handlers, scheduled jobs, and content providers.
  • -Installable add-ons and the wider .NET / NuGet package ecosystem.
  • -Integrations that run in-process rather than as separate services you host.
  • -Direct database and schema access, plus custom indexing and search backends.

The net: neither option removes the work, it moves it. SaaS hands backend hosting, versioning, and upgrades to Optimizely and keeps the CMS itself low-maintenance, in exchange for you owning the delivery stack and rebuilding anything beyond its feature surface outside the CMS. CMS 13 gives you a broader built-in feature set and a fully extensible backend, in exchange for you owning that code, the infrastructure, and the version and upgrade lifecycle.

The building blocks of a headless site #

A headless CMS site has more moving parts than a traditional coupled CMS because the concerns are separated. Here is every piece you need and what it does.

1

Headless CMS

Optimizely SaaS CMS in this demo

Where editors create, manage, and publish content. Stores content as structured data - text, images, references, rich text. Generates no HTML and knows nothing about how content will be displayed. Exposes content via a management API and fires webhooks when content is published.

2

Content Delivery API

Optimizely Graph in this demo

A read-optimized, globally distributed API that makes CMS content queryable by the frontend. In this demo it is a GraphQL API at cg.optimizely.com with its own CDN cache layer. The frontend queries this - not the CMS management API directly - because the delivery API is built for high-traffic reads, not authoring operations.

3

Frontend application

Next.js in this demo

The application that fetches content from the delivery API and renders it into HTML for visitors. Entirely your code, hosted wherever you choose. Because it is separate from the CMS, it can be rebuilt, rewritten, or replaced without touching the CMS or migrating content. The same CMS content can simultaneously serve a web app, a mobile app, and a third-party integration.

4

Frontend hosting and CDN

Vercel in this demo

Where the frontend application runs and where rendered pages are cached. The CDN stores pre-rendered copies of pages at edge nodes close to users and serves them in ~10-50ms without involving the frontend server. Any hosting provider that supports your framework works here - Vercel, Netlify, Cloudflare Pages, AWS, and others all handle this pattern.

5

Cache invalidation via webhook

Optimizely Graph fires a POST to /api/webhooks

When an editor publishes content, something needs to tell the frontend its cached pages are out of date. This is done via webhook - Optimizely Graph sends a POST request to the frontend's webhook endpoint, which marks the relevant cache entries as stale. The next visitor request triggers a background re-render with fresh content.

Architecture Diagram #

Request flow left to right. The CDN serves pages from its ISR cache; on a miss it forwards to Next.js, which renders from Optimizely Graph. CMS publishes sync into Graph, which fires a webhook to invalidate the ISR cache. SDK docs ↗

Browservisitor · editorEdge CDNISR full-route cacheone entry per page URLNext.js ServerApp Router · RSCISR · revalidateOptimizely Graphcg.optimizely.comGraphQL delivery APIOptimizely CMSauthoring UIVisual BuilderHTML responseHTTPSISR missGraphQLcontentcontent syncGraph webhook
HTTPS request
HTML response
CDN miss forwarded to Next.js
GraphQL query · content response
CMS content sync on publish
Graph webhook - ISR cache invalidation

System Components #

What each box in the diagram is responsible for.

Edge CDN / ISR Cache

  • -Stores the fully rendered HTML of every page, one entry per page URL. Any CDN that supports path-based caching can serve this - no custom cache configuration needed.
  • -TTL is set by export const revalidate in the catch-all route. Warm cache hits are served in ~10-50ms from the nearest edge node - the Next.js server is not involved.
  • -Busted on publish: the Graph webhook calls revalidatePath("/", "layout") + revalidateTag("page") which marks entries as stale.
  • -This layer absorbs almost all visitor traffic; the server only runs on a cache miss.

Next.js Server

  • -Renders CMS pages with ISR. No cookies() or headers() calls anywhere in the server render tree - these would force cache-control: no-store globally.
  • -Runs only on an ISR cache miss - the first request for a URL, or after a webhook marks it stale.
  • -Queries Optimizely Graph for the page content and renders React Server Components to HTML.
  • -Writes the rendered output back to the CDN cache so subsequent requests skip the server.

Optimizely Graph

  • -GraphQL delivery API at cg.optimizely.com. Serves CMS content, navigation, and banners.
  • -Read-optimized and globally distributed - built for high-traffic reads, not authoring operations.
  • -Has its own CDN cache layer independent of Next.js. Bypass with ?cache=false for preview/seed scripts.
  • -Fires a webhook to /api/webhooks on every content change: bulk.completed, doc.updated, doc.expired.

Optimizely CMS

  • -Authors create and manage pages, blocks, and navigation in Visual Builder.
  • -Stores content as structured data and generates no HTML of its own.
  • -On publish: content syncs to Optimizely Graph. Graph fires a webhook to trigger ISR invalidation.

Who Interacts and How #

Two distinct actors drive the system - content editors on the authoring side, and visitors on the delivery side. They never share a runtime.

Content Editor

Works entirely inside Optimizely CMS. Never interacts with the Next.js app directly.

  • -Opens Visual Builder - edits page composition, block content, and navigation.
  • -Clicks Publish. The CMS syncs the change to Optimizely Graph, which fires a POST webhook to /api/webhooks.
  • -The webhook marks the ISR cache as stale. The next visitor request triggers a background re-render. The editor does not wait for the CDN to clear.
  • -Preview mode bypasses the ISR cache entirely - the editor sees draft content via a previewToken that the app reads from the URL.

Visitor

Makes an HTTPS request. The CDN answers it, falling back to the Next.js server only on a cache miss.

  • -The request hits the CDN edge. A warm ISR cache hit returns the page in ~10-50ms with no server involvement.
  • -On a cache miss, the Next.js server renders the page from Graph data and caches the result. The visitor receives the same HTML either way.
  • -After the HTML arrives, React hydrates in the browser. Client components handle any interactivity - no server round-trip.
  • -Static assets (JS, CSS, images) are fetched from the CDN and cached in the browser.

Request Flow #

What happens between a browser request and the page appearing, step by step. SDK docs ↗

1

Browser sends HTTPS request

e.g. GET /en/investments/stocks-isa - arrives at Vercel's edge network.

2

Edge CDN checks the ISR cache

The requested URL is looked up. Cache HIT: the ISR-cached page is returned to the browser in ~10-50ms. Cache MISS: the request is forwarded to the Next.js server.

3

Next.js renders the page (ISR miss only)

The catch-all route queries Optimizely Graph for the page content - no cookies() or headers() calls. Renders React Server Components to HTML with export const revalidate. The rendered output is stored in the CDN cache.

4

HTML returned to browser

The response is served - from CDN on a hit, from Next.js on a miss. The browser receives identical HTML either way, then React hydrates for any client-side interactivity.

Publish Flow #

What happens when an editor publishes content in the CMS. For webhook endpoint details and ISR revalidation tag strategy, see the Caching demo.

1

Editor publishes in Optimizely CMS

Content is saved. The CMS begins syncing the change to Optimizely Graph.

2

Graph indexes the content

Optimizely Graph processes the change and makes the new content queryable via its GraphQL API.

3

Graph fires a POST webhook to /api/webhooks

A small JSON payload signals that content changed. The webhook handler calls revalidatePath("/", "layout") and revalidateTag() for page, navigation, banner, and quotes. Nothing is re-rendered yet - entries are just marked stale.

4

Next visitor gets the stale version instantly

ISR always serves the existing cached page first. The visitor does not wait. In the background Next.js re-renders the page with fresh data from Graph.

5

All subsequent requests get the updated page

The newly rendered output is stored in the CDN. No redeploy needed.

The Cache Layers #

A request can be answered at any of five layers between the visitor and the CMS. Each layer down adds latency, and each has its own invalidation story. This is the conceptual map - the ISR Caching demo covers the mechanics (tags, TTLs, and code).

1

Browser HTTP cache

~0ms - invalidated by hashed filenames

Hashed static assets (JS, CSS) are cached immutably in the visitor's browser - a new deploy produces new filenames, so stale assets are never served. HTML documents are not long-cached by the browser; every navigation revalidates against the CDN.

2

CDN edge / ISR full-route cache

~10-50ms - invalidated by the Graph webhook

Stores the fully rendered HTML of every page, one entry per page URL. A warm hit is served from the nearest edge node without touching the Next.js server. This is the layer that absorbs almost all visitor traffic.

3

Next.js Data Cache

saves the Graph round-trip - invalidated by revalidateTag()

A fetch-level cache inside the Next.js server, keyed per query with revalidate and tags (page 60s, navigation 300s). When a page re-renders, tagged data that is still fresh is reused instead of re-querying Graph. The webhook handler calls revalidateTag() to mark entries stale on publish.

4

Optimizely Graph CDN cache

independent of this app - managed by Optimizely

Graph runs its own CDN cache in front of its content index at cg.optimizely.com. Even when the Next.js Data Cache misses, a repeated query is often answered from Graph's cache rather than its index. Preview and seed scripts bypass it with ?cache=false.

5

Source of truth

CMS content, synced into Graph's index on publish

The bottom of the stack. Content lives in the CMS and is synced into Graph's index when an editor publishes. The CMS itself never serves visitor traffic. A full ISR-miss render (Next.js render plus Graph query) costs a few hundred milliseconds - and only happens on the first request for a URL or after invalidation.

What Happens When Something Is Down #

Separated concerns mean partial failure instead of total failure. What each outage actually does to the site - for the code-level patterns (error boundaries, fallbacks, not-found), see the Error Handling demo.

CMS down or in maintenance

  • -Graph keeps serving from its own index - it does not read from the CMS at request time.
  • -Visitors see no difference: ISR pages keep rendering with data from Graph.
  • -Editors are blocked from authoring until the CMS is back. Nothing published is lost.

Optimizely Graph unreachable

  • -Warm pages keep serving from the CDN ISR cache - a cache hit never queries Graph.
  • -When a render does happen, wrap each content query so a Graph failure returns static or cached fallback data instead of throwing - shared layout elements like navigation and footer keep rendering.
  • -Page content queries without a fallback surface to Next.js error boundaries rather than crashing the whole site.

Webhook missed or delayed

  • -The revalidate TTL is the backstop: page caches expire after their TTL and re-render on the next request.
  • -Worst case, visitors briefly see slightly stale content - then the next request triggers a background re-render with fresh Graph data.
  • -No manual intervention needed; the system self-heals on the next TTL expiry.

Media Has Its Own Delivery Path #

Images and other assets do not flow through Graph or the ISR HTML cache - they are delivered on a separate path with an independent cache lifecycle. See the Media & DAM demo for the asset workflow.

  • -Graph returns only the image URL, never the bytes - the rendered HTML contains an image tag pointing at the asset host.
  • -The browser fetches assets from the Optimizely asset hosts (*.cms.optimizely.com and *.cmp.optimizely.com - the allowed remotePatterns in next.config.ts).
  • -next/image sits in front: it resizes, converts to modern formats, and caches the optimized result on the hosting CDN.
  • -Consequence: publishing new content invalidates page HTML, but images keep serving from their own cache - media and pages age independently.

CDN Compatibility #

The ISR full-route cache is the layer that absorbs visitor traffic. It relies only on standard, portable CDN behaviour.

This demo deploys to Vercel, but the ISR pattern works with any CDN that caches by URL path - which is the default everywhere. Netlify, AWS CloudFront, Cloudflare, Akamai, and Fastly all serve path-keyed cache entries out of the box, so no custom cache configuration is needed. Freshness is driven by the publish webhook (revalidatePath / revalidateTag), with the per-route revalidate TTL as a backstop.

Key Terms #

Definitions of the technical terms used throughout this page.

CDNContent Delivery Network

A global network of servers that cache copies of your pages close to users. Instead of every request travelling to one central server, it is served from the nearest node - reducing latency from hundreds of milliseconds to tens.

Edge

Servers at CDN edge nodes - physically distributed around the world, close to users. Running code at the edge means it executes at these locations rather than a central server. Latency can drop from ~200ms to under 5ms.

ISRIncremental Static Regeneration

A Next.js feature. Pages are pre-rendered to static HTML and cached. They serve instantly from cache. After a set time (or on demand via webhook), the page is regenerated in the background with fresh data - no redeploy needed.

GraphQL

A query language for APIs. Instead of many fixed endpoints (like REST), you send one query describing exactly the data you want and get exactly that back - no over-fetching, no under-fetching. Optimizely Graph exposes its content delivery API via GraphQL.

Webhook

When something happens in one system, it automatically sends an HTTP POST request to notify another system. When an editor publishes in Optimizely CMS, Optimizely Graph fires a webhook to tell this Next.js app to invalidate its cache.

RSCReact Server Components

A Next.js and React feature where components render on the server and send HTML to the browser instead of JavaScript that runs client-side. Used for CMS content rendering so the page arrives pre-rendered and cacheable.