Developer Demo

Optimizely One Platform

How Graph, SaaS CMS, DAM, the Content Source API, Feature Experimentation, Web Experimentation, ODP, recommendations and Mark AI compose inside a single Next.js application - and which of them are actually wired up in this demo.

Key Things to Know#

  • Graph is the only read API. CMS content, DAM asset metadata and external systems registered through the Content Source API all converge on it, so the app has one query language and one cache story instead of one integration per product.
  • Experimentation happens before rendering. FX decides in edge middleware and the variation is encoded into the URL as __v_flag--variation, which gives every bucket its own stable ISR cache entry. Personalised pages stay statically cached instead of going dynamic.
  • Client-side tools can still reach server-rendered output. Web Experimentation runs in the browser but writes a cookie that middleware reads on the next request, so the variation survives into the server render with no flash.
  • ODP closes the loop. Behavioural events go out, audience membership comes back, and a segment is mapped to a Graph variation key. Data collected on one request changes the content served on the next.
  • Two of these products are not integrated here. Content Recommendations and Product Recommendations are drawn as reference boxes, and Mark AI is authoring-side only. See the status table.

Architecture Diagram #

Content supply on the left, Graph as the hub, the Next.js app in the middle split by runtime, and the decisioning and data products on the right. Solid boxes are wired up in this repo; dashed boxes are reference architecture.

CONTENT SUPPLYDELIVERY HUBAPPLICATION RUNTIMEDECISION + DATAone Next.js deployment, three runtimesOptimizely SaaS CMSauthoring · Visual BuilderManagement API · previewpublishes into GraphDAM / CMP Assetsasset metadata via Graphbinaries via CMP CDNon-the-fly resizeContent Source APIsystems that are not the CMSPIM · branches · quotesindexed as Graph typestyped fields · GeoPointMark AICMS MCP serverauthoring + review agentsdev-time todayOptimizely Graphcg.optimizely.comone GraphQL read APIfor CMS · DAM meta ·external sourcessearch · facets · geovariation filter forpersonalised contentEdge MiddlewareFX decideAll at the edgerewrites __v_flag--variationone stable ISR key per bucketCMS redirects · visitor idServer Components (RSC)"use cache" + cacheTagGraph variation filterODP segment read on /renders CMS compositionsClient / BrowserWX snippet (blocking)ODP zaius tagAutoTracker · mb_* eventsFX browser datafileFeature Experimentationdatafile · 60s TTLdecides at the edge, on theserver and in the browserWeb Experimentationclient-side snippetshares the visitor cookiebridges into middlewareData Platform (ODP)events in: zaius tag andserver /v3/eventssegments out: /v3/graphqlaudiences → variation keyContent Recommendationsreference - not wired upwould read the ODP profileProduct Recommendationsreference - not wired upcatalog feed + behaviourGraphQLcontentpublish webhook · revalidateTagdatafile · decideAllopti_wx_variation cookiemb_* eventssegments
CMS, DAM and external sources index into Graph
Next.js queries Graph (GraphQL)
Graph returns content
Publish webhook - revalidateTag drops the ISR entry
FX and WX decisions reach the app
WX variation cookie, read by middleware next request
Behavioural events to ODP
ODP audiences back to the server as a variation key
Mark AI agents assist authoring (dev-time, via MCP)
Reference only - not integrated in this repo
Solid border - integrated in this repo
Dashed border - reference architecture, not integrated

The Three Feedback Loops #

Boxes and arrows show what talks to what. These loops are the reason the stack is worth composing: each one carries a signal from one product back into another and changes what the next request renders.

Data to decision to content

Behaviour observed in the browser becomes audience membership in ODP, which becomes a variation key in a Graph query. The content served changes because of what the visitor did earlier.

mb_* event

AutoTracker

ODP profile

zaius tag

Audience

/v3/graphql

variation key

resolveVariationKey()

Graph filter

includeOriginal

Personalised page

RSC render

Publish to invalidate

Nothing polls. An editor publishing is what makes the cache drop, and the one-hour TTL is only the ceiling if a webhook is ever missed.

Editor publishes

Visual Builder

Graph reindexes

~30-60s

Webhook

doc.updated

revalidateTag

every CACHE_TAG

Next request

rerenders once

Client decision to server render

The loop that makes a client-side tool safe on a statically cached site. WX picks its variation in the browser, but the visible render on the next navigation is server-side, so there is no flash of the original.

WX snippet

browser

opti_wx_variation

cookie

Middleware

validates vs datafile

__v_ segment

URL rewrite

Cached variant

own ISR entry

Product by Product #

What each product does in this architecture, how it is actually connected, and where that connection lives in the code.

Optimizely Graph

Wired up

The single read API. Every content-shaped thing in the stack converges here: CMS pages, DAM asset metadata, and external systems registered through the Content Source API.

Server-side GraphQL over the cms-sdk GraphClient against cg.optimizely.com, single-key auth. Cached queries are "use cache" functions wrapping request() with a cacheTag; unbounded-input queries (search, autocomplete, geo) call request() directly and stay uncached.

  • src/lib/optimizely/graphClient.ts
  • src/lib/graphql/queries/

SaaS CMS

Wired up

Authoring, Visual Builder compositions, display templates, preview and the editorial workflow. Publishes sync into Graph rather than being read directly by the app.

Content types pushed from code via opti:push. Reads go through Graph; writes (seeding, migrations) go through the Management API with an OAuth client-credentials token. Preview uses a short-lived token, or App Key Basic auth for shareable external links.

  • optimizely.config.mjs
  • src/lib/optimizely/componentRegistry.ts
  • scripts/_shared.ts

Feature Experimentation

Wired up

Flags, A/B tests and server-side bucketing. The decision that matters most is made before rendering, in middleware, so each variation can be cached independently.

Datafile-only SDK (60s TTL) running in three places: edge middleware (decideAll, encoded into the URL), server components via getOptimizelyUser(), and the browser for impressions. Impressions are suppressed by default and fired once by the component that actually renders the variant.

  • src/middleware.ts
  • src/lib/optimizely/user.ts
  • src/lib/optimizely/variationPath.ts

Data Platform (ODP)

Wired up

The behavioural profile store. Events flow out of the app, come back as audience membership, and are mapped to a Graph variation key so the next render is personalised.

Three channels: the client zaius tag for pageviews and mb_* events, a server POST to /v3/events for form submissions, and a server /v3/graphql query for audience membership (5 min cache). Segments map to variation keys through ODP_SEGMENT_TO_VARIATION.

  • src/lib/optimizely/odp.ts
  • src/components/OdpSetup.tsx
  • src/components/AutoTracker.tsx

Web Experimentation

Wired up

Visual, marketer-owned client-side testing on top of the same visitor identity as FX. Useful for changes that do not need a code deploy.

A blocking snippet in <head> (sync on purpose, to avoid a flash of the original). A WX custom-JS action writes an opti_wx_variation cookie, which middleware validates against the FX datafile and folds into the URL segment, so the next request is server-rendered. FX decisions take precedence.

  • src/app/layout.tsx
  • src/middleware.ts

DAM / CMP Assets

Wired up

Asset library and delivery. Images arrive as references on content and are rendered with responsive srcsets and on-the-fly CDN resizing.

Delivery-side only: asset metadata comes through Graph on the content reference, binaries come from the CMP CDN with resize params, and an allow-listed proxy route adds long-lived caching. There are no direct DAM REST calls.

  • src/lib/optimizely/damImage.ts
  • src/app/api/image-proxy/route.ts

Content Source API

Wired up

Brings systems that are not the CMS into Graph, so a PIM, a branch database or a quote feed can be queried with the same GraphQL and the same facets as CMS content.

Build and seed time: a schema is PUT to the content source endpoint, then rows are pushed as NdJSON under App Key Basic auth. Once indexed, the items read back exactly like CMS content, including GeoPoint geo search.

  • scripts/_contentSource.ts
  • src/lib/graphql/queries/GetLocations.ts

Mark AI

Partial

AI agents over the stack: content generation and review, SEO and GEO analysis, and natural-language authoring against the CMS.

Present as documentation plus a dev-time MCP connection (the CMS MCP server, and an experimentation MCP server in .mcp.json) that a developer or editor drives from their tooling. There is no runtime call from the app itself, so this is an authoring-side accelerator rather than a request-path dependency.

  • src/app/demo/mark-ai/page.tsx
  • src/app/demo/mcp-server/page.tsx
  • .mcp.json

Content Recommendations

Reference

Picks the next best article or page per visitor from their behavioural profile, typically for a "recommended for you" rail.

Not integrated here. Wiring it up means adding the recs script or API, letting it read the ODP profile keyed on the same optimizelyEndUserId cookie, and resolving the returned content keys through Graph so the rail renders with the existing block components.

Product Recommendations

Reference

Catalog-driven merchandising: also-bought, trending and personalised product rails.

Not integrated here, and it needs something this demo does not have: a product catalog feed. The natural fit would be to register the catalog through the Content Source API so products are queryable in Graph, then feed behavioural signals from the existing mb_* event layer.

Request Lifecycle #

One request for a CMS page, from the edge to the rendered HTML, plus the invalidation that follows a publish. This is the path the diagram traces.

  1. 1

    Edge middleware

    Mints or reads the optimizelyEndUserId cookie, applies CMS-managed redirects, runs FX decideAll against the cached datafile, keeps only flags marked as CMS flags whose route matches, and rewrites the URL with up to three __v_flag--variation segments. The WX cookie is folded in here too, only where FX has no decision for that flag.

  2. 2

    Catch-all route

    extractVariations() splits the variation segments back off the slug, then buildUrlCandidates() produces the locale-aware URL candidates to try. On the homepage only, with no FX variation, the ODP segment read runs and the route opts out of caching - which is why / is dynamic and every other route stays on ISR.

  3. 3

    Graph query

    getContentByPath() runs per candidate with a variation filter that always sets includeOriginal: true, so a visitor who matches no variation still gets the original page. A key-lookup fallback covers the case where the path query misses.

  4. 4

    Render

    The component registry dispatches the returned type to an experience or page component, which walks the composition tree and renders each block. The variation actually served is reported back to FX as an impression from the client.

  5. 5

    Invalidate

    An editor publishes, Graph reindexes and fires its webhook, and the webhook route calls revalidateTag for every tag in CACHE_TAGS. The one-hour TTL is only the fallback ceiling.

What Is Actually Wired Up #

The diagram is a reference architecture, so it includes products this demo does not use. This table is the honest version: what you can go and read the code for today.

ProductStatusIn this repo
Optimizely GraphWired upCore read path, 19 named queries
SaaS CMSWired upManagement API, preview, Visual Builder
Feature ExperimentationWired upMiddleware, server and browser runtimes
Data Platform (ODP)Wired upzaius tag, /v3/events, /v3/graphql segments
Web ExperimentationWired upBlocking snippet plus cookie bridge
DAM / CMPWired upDelivery side only, no DAM REST calls
Content Source APIWired upSeed time: quotes and branch locations
Mark AIPartialDocs and dev-time MCP, no runtime integration
Content RecommendationsReferenceNot integrated
Product RecommendationsReferenceNot integrated