diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/.env.example b/implementations/react-web-sdk+node-sdk_nextjs-ssr/.env.example
new file mode 100644
index 00000000..9273d7d6
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/.env.example
@@ -0,0 +1,16 @@
+DOTENV_CONFIG_QUIET=true
+
+PUBLIC_NINETAILED_CLIENT_ID="mock-client-id"
+PUBLIC_NINETAILED_ENVIRONMENT="main"
+
+PUBLIC_EXPERIENCE_API_BASE_URL="http://localhost:8000/experience/"
+PUBLIC_INSIGHTS_API_BASE_URL="http://localhost:8000/insights/"
+
+PUBLIC_CONTENTFUL_TOKEN="mock-token"
+PUBLIC_CONTENTFUL_PREVIEW_TOKEN="mock-preview-token"
+PUBLIC_CONTENTFUL_ENVIRONMENT="master"
+PUBLIC_CONTENTFUL_SPACE_ID="mock-space-id"
+
+PUBLIC_CONTENTFUL_CDA_HOST="localhost:8000"
+PUBLIC_CONTENTFUL_BASE_PATH="contentful"
+PUBLIC_OPTIMIZATION_ENABLE_PREVIEW_PANEL="false"
diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/.gitignore b/implementations/react-web-sdk+node-sdk_nextjs-ssr/.gitignore
new file mode 100644
index 00000000..818f88a7
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/.gitignore
@@ -0,0 +1,42 @@
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+/playwright-report
+/test-results
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files
+.env
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/.npmrc b/implementations/react-web-sdk+node-sdk_nextjs-ssr/.npmrc
new file mode 100644
index 00000000..135f7a0d
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/.npmrc
@@ -0,0 +1 @@
+shared-workspace-lockfile=false
diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/AGENTS.md b/implementations/react-web-sdk+node-sdk_nextjs-ssr/AGENTS.md
new file mode 100644
index 00000000..436a41b1
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/AGENTS.md
@@ -0,0 +1,45 @@
+# AGENTS.md
+
+Read the repository root `AGENTS.md` first.
+
+## Scope
+
+This is the Next.js SSR hybrid reference implementation. The Node SDK resolves entries on the server
+(personalization is SSR). The React SDK hydrates on the client for event tracking and interactive
+controls (consent, identify, page views, clicks).
+
+This represents a customer setup where:
+
+- Personalized content is resolved server-side for fast first paint
+- Client-side JS is only used for tracking and interactive features
+- The same anonymous profile cookie bridges server and client
+
+## Key Paths
+
+- `app/` — Next.js App Router (single page, Server Component)
+- `lib/` — SDK config, Contentful client, Node SDK singleton
+- `components/` — ClientProviderWrapper (React SDK), InteractiveControls
+- `middleware.ts` — cookie lifecycle
+- `.env.example`
+
+## Local Rules
+
+- Next.js App Router only. No Pages Router.
+- Server Components must not import from `@contentful/optimization-react-web`.
+- Client components (`"use client"`) must not import from `@contentful/optimization-node`.
+- Use the SDK's `OptimizationRoot` directly — no custom provider wrappers around it.
+- If you changed a consumed package, run `pnpm build:pkgs` and reinstall before trusting results.
+
+## Commands
+
+- `pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr implementation:install`
+- `pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr typecheck`
+- `pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr build`
+- `pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr dev`
+- `pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr serve`
+- `pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr serve:stop`
+
+## Usually Validate
+
+- Run `typecheck` for local code changes.
+- Run `build` when changing production bundling behavior.
diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/README.md b/implementations/react-web-sdk+node-sdk_nextjs-ssr/README.md
new file mode 100644
index 00000000..dc59a3ff
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/README.md
@@ -0,0 +1,192 @@
+# Next.js SSR + Client Events Reference Implementation
+
+`react-web-sdk+node-sdk_nextjs-ssr` — Next.js App Router reference using
+`@contentful/optimization-node` for server-side entry resolution and
+`@contentful/optimization-react-web` for client-side event tracking and interactive controls.
+
+## Pattern: SSR-Primary with CSR Analytics
+
+This setup is the simplest and most robust Next.js personalization pattern. The server owns all
+personalization decisions. The client owns all analytics and interactive concerns.
+
+### Why this pattern?
+
+- **No flicker.** Personalized content is in the HTML from the server. No loading states, no
+ client-side variant swaps.
+- **Full SEO.** Search engines see the resolved personalized content.
+- **Minimal client JS.** Content rendering requires zero JavaScript. Only tracking and interactive
+ controls (consent, identify) need client hydration.
+- **No Next.js SDK needed.** The Node SDK (stateless) works in Server Components and Middleware. The
+ React Web SDK (stateful) works in Client Components. No framework-specific glue package required.
+
+### Responsibility split
+
+| Concern | Where it runs | SDK used |
+| ------------------------------------------------ | ------------------------- | ------------------------------------------- |
+| Anonymous ID cookie lifecycle | Middleware (Edge Runtime) | Node SDK |
+| Profile resolution (`sdk.page()`) | Server Component | Node SDK |
+| Entry variant resolution | Server Component | Node SDK (`resolveOptimizedEntry`) |
+| HTML rendering of personalized content | Server Component | None (plain React) |
+| Page view tracking | Client (after hydration) | React Web SDK (`NextAppAutoPageTracker`) |
+| Entry interaction tracking (views/clicks/hovers) | Client (after hydration) | React Web SDK (`autoTrackEntryInteraction`) |
+| Consent management | Client (after hydration) | React Web SDK (`sdk.consent()`) |
+| User identification | Client (after hydration) | React Web SDK (`sdk.identify()`) |
+
+### Behavioral expectations
+
+Once the page is served, the personalized content is **static until the next server roundtrip**.
+Client-side actions like granting consent or identifying the user update the Web SDK's internal
+state and fire analytics events, but they do **not** cause the page content to re-render or swap
+variants. The user sees the updated personalization only on the next full navigation (a new server
+request where the Node SDK re-resolves entries with the updated profile).
+
+This is intentional: the server is the single source of truth for what content to show. The client
+never contradicts what the server rendered.
+
+## Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ REQUEST PHASE (Server) │
+│ │
+│ 1. Middleware (Edge Runtime) │
+│ ├─ Read `ctfl-opt-aid` cookie from request │
+│ ├─ Call Node SDK `sdk.page()` with request context + profile │
+│ └─ Set `ctfl-opt-aid` cookie on response with profile.id │
+│ │
+│ 2. Server Component (page.tsx) │
+│ ├─ Read `ctfl-opt-aid` cookie (set by middleware in same cycle) │
+│ ├─ Fetch Contentful entries from CDA (in parallel) │
+│ ├─ Call Node SDK `sdk.page()` → get selectedOptimizations │
+│ ├─ For each entry: `sdk.resolveOptimizedEntry(entry, selected)` │
+│ └─ Render resolved entries as plain HTML │
+│ │
+│ ↓ HTML response with personalized content │
+└─────────────────────────────────────────────────────────────────────┘
+
+┌─────────────────────────────────────────────────────────────────────┐
+│ HYDRATION PHASE (Browser) │
+│ │
+│ 3. ClientProviderWrapper (dynamic, ssr: false) │
+│ ├─ OptimizationRoot initializes Web SDK │
+│ ├─ Reads `ctfl-opt-aid` cookie → same identity as server │
+│ ├─ NextAppAutoPageTracker fires page view event │
+│ └─ autoTrackEntryInteraction observes elements with │
+│ data-ctfl-entry-id attributes (views, clicks, hovers) │
+│ │
+│ 4. InteractiveControls (client component) │
+│ ├─ Subscribes to sdk.states.consent / sdk.states.profile │
+│ ├─ Renders consent toggle button │
+│ └─ Renders identify / reset buttons │
+│ │
+│ Note: No content re-rendering happens client-side. │
+│ Content remains as server-rendered until next navigation. │
+└─────────────────────────────────────────────────────────────────────┘
+```
+
+## Key implementation patterns
+
+### 1. Cookie as the identity bridge
+
+The `ctfl-opt-aid` cookie is the **only shared state** between server and client. Middleware creates
+it, the Server Component reads it, and the Web SDK reads it from `document.cookie` after hydration.
+This ensures both sides operate on the same anonymous profile.
+
+```typescript
+// middleware.ts
+const anonymousId = request.cookies.get(ANONYMOUS_ID_COOKIE)?.value
+const profile = anonymousId ? { id: anonymousId } : undefined
+const data = await sdk.page({ ...requestContext, profile })
+response.cookies.set(ANONYMOUS_ID_COOKIE, data.profile.id, { path: '/', sameSite: 'lax' })
+```
+
+### 2. Node SDK as a module-level singleton
+
+The Node SDK is stateless and safe to reuse across requests. A single instance is created at module
+load and imported by both middleware and page:
+
+```typescript
+// lib/optimization-server.ts
+import ContentfulOptimization from '@contentful/optimization-node'
+const sdk = new ContentfulOptimization({ clientId, environment, api })
+export { sdk }
+```
+
+### 3. React Web SDK loaded only on the client
+
+The Web SDK depends on browser APIs (`localStorage`, `document.cookie`, `IntersectionObserver`).
+Using `next/dynamic` with `ssr: false` prevents any server-side instantiation:
+
+```typescript
+// components/ClientProviderWrapper.tsx
+const OptimizationRoot = dynamic(
+ () =>
+ import('@contentful/optimization-react-web').then((mod) => ({ default: mod.OptimizationRoot })),
+ { ssr: false },
+)
+```
+
+### 4. Server Components never import from the React Web SDK
+
+This is a hard rule. Server Components use `@contentful/optimization-node` only. Client Components
+(`"use client"`) use `@contentful/optimization-react-web` only. Mixing them causes runtime errors or
+bundling issues.
+
+### 5. Data attributes for automatic interaction tracking
+
+Server-rendered entries include `data-ctfl-entry-id` and `data-ctfl-baseline-id` attributes. After
+hydration, the Web SDK's `autoTrackEntryInteraction` uses a MutationObserver to detect these
+elements and registers IntersectionObserver (views), click listeners, and hover listeners
+automatically:
+
+```tsx
+
+ {/* content */}
+
+```
+
+## When does the user see updated personalization?
+
+| User action | Effect on displayed content | When personalization updates |
+| ------------------------------------------ | --------------------------------------- | ---------------------------- |
+| First page load (anonymous) | Baseline or variant per profile | Immediate (server-resolved) |
+| Grant/reject consent | No change to content | Next server request |
+| Identify (`sdk.identify()`) | No change to content | Next server request |
+| Navigate to another page (full navigation) | New server-resolved content | Immediate (new SSR) |
+| Browser refresh | Server re-resolves with updated profile | Immediate (new SSR) |
+
+The key insight: **client actions update the profile server-side (via the Experience API)**, but the
+rendered content is only a snapshot of the profile state at the time of the server request. The next
+request will reflect the updated profile.
+
+## Setup
+
+```bash
+pnpm build:pkgs
+pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr implementation:install
+cp implementations/react-web-sdk+node-sdk_nextjs-ssr/.env.example implementations/react-web-sdk+node-sdk_nextjs-ssr/.env
+```
+
+## Development
+
+```bash
+pnpm implementation:run -- react-web-sdk+node-sdk_nextjs-ssr dev
+```
+
+## When to use this pattern
+
+- Content-heavy marketing sites where SEO and first-paint performance matter
+- Sites where personalization is based on profile traits, audience segments, or geo — not real-time
+ interactions within the same page
+- Teams that want the simplest mental model: server decides what to show, client tracks what
+ happened
+- Sites already using Next.js App Router with Server Components
+
+## When NOT to use this pattern
+
+- If you need instant client-side variant swaps after identify (e.g., "Welcome back, Charles!"
+ appearing without a page refresh) — consider (Hybrid SSR + CSR takeover)
+- If your site is a pure SPA with no server rendering — use the React Web SDK directly (see
+ `react-web-sdk` implementation)
+- If you need edge-side personalization for static/cached pages — consider a middleware-based ESR
+ pattern with `resolveOptimizedEntry` at the edge
diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/globals.css b/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/globals.css
new file mode 100644
index 00000000..d4b50785
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/globals.css
@@ -0,0 +1 @@
+@import 'tailwindcss';
diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/layout.tsx b/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/layout.tsx
new file mode 100644
index 00000000..3f6c44b9
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/layout.tsx
@@ -0,0 +1,23 @@
+import { ClientProviderWrapper } from '@/components/ClientProviderWrapper'
+import type { Metadata } from 'next'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'Optimization Next.js SSR Hybrid',
+ description:
+ 'Next.js App Router reference: Node SDK resolves entries server-side, React SDK handles client-side tracking and interactive controls.',
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/page.tsx b/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/page.tsx
new file mode 100644
index 00000000..b4adc6af
--- /dev/null
+++ b/implementations/react-web-sdk+node-sdk_nextjs-ssr/app/page.tsx
@@ -0,0 +1,86 @@
+import { InteractiveControls } from '@/components/InteractiveControls'
+import { ENTRY_IDS } from '@/config/entries'
+import { fetchEntries } from '@/lib/contentful-client'
+import { sdk } from '@/lib/optimization-server'
+import type { ContentEntry } from '@/types/contentful'
+import { ANONYMOUS_ID_COOKIE } from '@contentful/optimization-node/constants'
+import { cookies, headers } from 'next/headers'
+
+function getEntryText(entry: ContentEntry): string {
+ return typeof entry.fields.text === 'string' ? entry.fields.text : 'No content'
+}
+
+function ServerRenderedEntry({
+ baselineEntry,
+ resolvedEntry,
+}: {
+ baselineEntry: ContentEntry
+ resolvedEntry: ContentEntry
+}) {
+ return (
+
+ Entries are resolved on the server via the Node SDK. The HTML contains personalized content
+ with zero client-side JavaScript for rendering. The React SDK hydrates for event tracking
+ and interactive controls (consent, identify).
+