> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/developers/docs/_mcp/server.

# Interaction tracking in Web SDKs

> Understand how browser interaction tracking works in `@contentful/optimization-web` and the React layer provided by `@contentful/optimization-react-web`.

## Overview

Use this document to understand how browser interaction tracking works in
`@contentful/optimization-web` and the React layer provided by `@contentful/optimization-react-web`.
It explains how entry views, clicks, hovers, Custom Flag views, page events, and custom events move
from browser behavior to Core event delivery.

For setup steps, see
[Integrate the Optimization Web SDK in a web app](/personalization/optimization-sdk/integrate-the-web-sdk-in-a-web-app/)
and
[Integrate the Optimization React Web SDK in a React app](/personalization/optimization-sdk/integrate-the-react-web-sdk-in-a-react-app/).
For server-owned rendering, see
[Interaction tracking in Node and stateless environments](/personalization/optimization-sdk/interaction-tracking-in-node-and-stateless-environments/).

## Tracking boundary

Interaction tracking has two separate jobs:

* **Detection** - Observing browser state or application calls and deciding that an interaction
  happened.
* **Delivery** - Building a valid Optimization event, applying consent and profile gates, and
  sending the event to the correct API.

The Web SDK owns browser detection for Contentful entry views, clicks, and hovers. Core owns event
construction, consent checks, queues, profile state, and API delivery. React Web does not implement
a separate tracking engine; it renders Web SDK tracking metadata and exposes the underlying Web SDK
tracking API through React providers and hooks.

This split keeps the browser-specific code small. A view observer can focus on
`IntersectionObserver`, timers, and DOM lifecycle. Core can focus on whether a `trackView()` call is
allowed, which event type it becomes, and which queue receives it.

## Layer responsibilities

| Layer                                | Responsibility in interaction tracking                                                                                                                                                                                |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@contentful/optimization-core`      | Builds `page`, `identify`, `track`, entry view, entry click, entry hover, and Custom Flag view events. Applies consent gates. Queues Experience API and Insights API work.                                            |
| `@contentful/optimization-web`       | Initializes Core for a browser runtime. Persists consent and, when persistence consent permits it, profile data, selected optimizations, and anonymous IDs. Discovers tracked DOM elements and observes interactions. |
| `@contentful/optimization-react-web` | Creates and tears down the Web SDK instance, resolves entries in React, emits `data-ctfl-*` attributes, exposes the SDK instance, and emits router-driven `page()` calls.                                             |

The application owns rendering policy, consent UX, identity policy, route ownership, and any
business event taxonomy passed to `track()`. Contentful entry fetching is application-owned unless
the SDK is explicitly configured with a consumer-owned `contentful.js` client for managed entry
fetching.

## Runtime prerequisites and defaults

This document applies to the browser Web SDK and the React Web layer. The Web SDK surface is the
`ContentfulOptimization` instance and its `tracking.*` APIs. React Web passes tracking-related props
through `OptimizationRoot`, renders metadata with `OptimizedEntry`, and exposes the same underlying
SDK instance through hooks.

Before relying on automatic entry interaction tracking, account for these runtime conditions:

| Concern                     | Default or prerequisite                                                                                                                                                                               |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Profile state               | Call `page()` or `identify()` before relying on Insights events. Insights delivery needs a current profile in SDK state.                                                                              |
| Consent                     | Browser SDKs allow `identify` and `page` before consent by default. Other event types need accepted consent or an explicit `allowedEventTypes` entry.                                                 |
| Automatic tracking          | Entry view, click, and hover detectors are configured on by default, but each detector starts only when the matching event type is allowed by consent or `allowedEventTypes`.                         |
| Persistence                 | Consent is read from `localStorage` during startup. Profile-continuity state and the anonymous ID cookie are persisted only when persistence consent permits it.                                      |
| Storage and offline support | Browser storage writes are best-effort. If a storage write fails, live SDK state continues in memory for the current runtime. Experience events queue while offline up to the configured queue limit. |
| Startup defaults            | `defaults` can seed consent, persistence consent, profile, selected optimizations, and related state before application events run.                                                                   |

## Event paths

Tracking events use two API paths:

| Path           | Methods                                                                     | Main effect                                                                 |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Experience API | `page()`, `identify()`, `screen()`, `track()`, sticky `trackView()`         | Evaluates or updates a profile and returns accepted/data event results.     |
| Insights API   | non-sticky `trackView()`, `trackClick()`, `trackHover()`, `trackFlagView()` | Sends Analytics events for entry interactions and Custom Flag observations. |

The Web SDK normally starts a browser journey with `page()` or `identify()`. Those calls use the
Experience API and populate the state that later Insights events need for attribution. If an
Insights event is emitted before the stateful SDK has a profile, Core logs a warning and drops that
Insights event because there is no profile to attach to the batch.

The entry interaction methods map to these wire event types:

| SDK method        | Wire type                                    | Common source                                                            |
| ----------------- | -------------------------------------------- | ------------------------------------------------------------------------ |
| `trackView()`     | `component`                                  | `IntersectionObserver` entry view detection, or direct application call. |
| `trackClick()`    | `component_click`                            | Document click listener, or direct application call.                     |
| `trackHover()`    | `component_hover`                            | Pointer or mouse hover listener, or direct application call.             |
| `trackFlagView()` | `component` with `componentType: 'Variable'` | `getFlag()` and `states.flag(name)` reads from stateful Core.            |

When configuring `allowedEventTypes`, entry views require `component`, clicks require
`component_click`, and hovers require `component_hover`. Custom Flag views can use `flag` or
`component`; use `flag` when the pre-consent allow-list must admit Custom Flag views without also
admitting entry views.

Sticky entry views touch both paths. When `trackView({ sticky: true, ... })` is called, Core sends
the view through Experience first, then sends an Insights view event. Non-sticky views only use
Insights.

Third-party analytics integrations that need one exposure for a sticky view must dedupe by semantic
fields such as `viewId`, `componentId`, `experienceId`, and `variantIndex`, not by `messageId`.

## Consent and profile gates

The Web SDK defaults `allowedEventTypes` to `['identify', 'page']`. Until consent is granted or an
event type is allow-listed, Core blocks non-allowed event types such as `track`, `component`,
`component_click`, and `component_hover`.

Consent affects two parts of automatic entry tracking:

* **Observer startup** - Entry interaction detectors are enabled by default, but they start only
  when consent or `allowedEventTypes` permits the matching event type. A manual
  `tracking.enableElement(...)` override can force observation while global automatic tracking is
  off, but only after that interaction type is allowed. Before consent or allow-list admission, the
  detector stays stopped and automatic tracking does not call Core, so no automatic blocked-event
  payload is produced for that element. Calling `consent(false)` stops detectors that are not
  otherwise allow-listed.
* **Event delivery** - Every event call still passes through Core's consent guard. Manual
  application calls, such as `trackView()` or `trackClick()`, are still blocked unless consent or
  `allowedEventTypes` permits the event type.

This means a typical browser integration needs both:

1. A profile-producing event such as `page()` or `identify()`.
2. Consent or an explicit event allow-list for the interaction event types the application wants to
   send.

Blocked events are observable through `onEventBlocked` and `states.blockedEventStream`. Individual
payloads blocked by consent are not replayed later when consent changes. SDK-owned current-state
surfaces, such as active page or Custom Flag state, can emit fresh events after consent opens when
the underlying state is still current and the event has not already been accepted.

## Tracked entry metadata

Automatic tracking starts from DOM metadata. The Web SDK looks for HTML or SVG elements with a
non-empty `data-ctfl-entry-id` attribute.

| Attribute                           | Used for                                                                                             |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `data-ctfl-entry-id`                | Required. Becomes `componentId` in entry interaction events.                                         |
| `data-ctfl-optimization-id`         | Optional. Becomes `experienceId`.                                                                    |
| `data-ctfl-optimization-context-id` | Optional. Runtime-owned context for follow-up event enrichment and diagnostics.                      |
| `data-ctfl-sticky`                  | Optional. `true` sends the first successful view for the element through the sticky Experience path. |
| `data-ctfl-variant-index`           | Optional. Non-negative integer used as `variantIndex`; invalid or unsafe values are ignored.         |
| `data-ctfl-track-views`             | Optional. `true` or `false` override for view observation when the view detector is running.         |
| `data-ctfl-track-clicks`            | Optional. `true` or `false` override for click observation when the click detector is running.       |
| `data-ctfl-track-hovers`            | Optional. `true` or `false` override for hover observation when the hover detector is running.       |
| `data-ctfl-clickable`               | Optional. `true` marks a non-semantic element as part of a clickable path for click tracking.        |

The tracking payload uses the resolved entry ID, not the baseline entry ID. When an application
needs the baseline ID for rerendering, store it separately, for example in `data-ctfl-baseline-id`.
The Web SDK does not use `data-ctfl-baseline-id` in event payloads.

`optimizationContextId` is SDK-owned runtime context. Core uses it to enrich follow-up event stream
emissions and diagnostics with the resolved entry and selected optimization context before building
API events. It is not an Experience API or Insights API event field.

Manual element observation can provide the same metadata without DOM attributes:

```ts
const resolved = optimization.resolveOptimizedEntry(baselineEntry)

optimization.tracking.enableElement('views', element, {
  data: {
    entryId: resolved.entry.sys.id,
    optimizationContextId: resolved.optimizationContextId,
    optimizationId: resolved.selectedOptimization?.experienceId,
    sticky: resolved.selectedOptimization?.sticky,
    variantIndex: resolved.selectedOptimization?.variantIndex,
  },
})
```

When manual `data` is valid, it takes precedence over `data-ctfl-*` values on the element. If manual
data is missing or invalid, the detector falls back to the element attributes.

## Runtime control and precedence

Automatic tracking defaults to enabled for entry views, clicks, and hovers. Pass `false` for any
interaction type that the application does not observe:

```ts
const optimization = new ContentfulOptimization({
  spaceId: 'your-space-id',
  autoTrackEntryInteraction: {
    hovers: false,
  },
})
```

The same runtime can be controlled after initialization:

| API                                                      | Effect                                                                                             |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `tracking.enable(interaction, options?)`                 | Enables automatic observation for `'views'`, `'clicks'`, or `'hovers'`.                            |
| `tracking.disable(interaction)`                          | Disables automatic observation for that interaction and stops it when no manual overrides need it. |
| `tracking.enableElement(interaction, element, options?)` | Force-enables one element, even when global automatic tracking for that interaction is off.        |
| `tracking.disableElement(interaction, element)`          | Force-disables one element.                                                                        |
| `tracking.clearElement(interaction, element)`            | Removes the manual override so the element falls back to attribute overrides or automatic state.   |

For each element, the runtime resolves tracking in this order:

1. Manual API override from `enableElement(...)` or `disableElement(...)`.
2. Per-element `data-ctfl-track-*` override.
3. Global automatic tracking state and the element registry.

`reset()` stops interaction trackers and clears manual element overrides. `destroy()` also
disconnects DOM observers and browser lifecycle listeners.

## DOM discovery

The Web SDK has one shared entry element registry. When an interaction detector starts, the registry
does two things:

* Seeds the initial set from `document.querySelectorAll('[data-ctfl-entry-id]')`.
* Subscribes to a `MutationObserver` that watches child additions, child removals, and
  `data-ctfl-entry-id` attribute mutations in the document subtree.

When a tracked element is added, the registry notifies the running view, click, and hover detectors.
When a tracked element is removed, the registry notifies those detectors so they can unobserve the
element or stop treating it as tracked.

The registry observes element existence and `data-ctfl-entry-id` mutations, not arbitrary attribute
changes. Payload attributes such as `data-ctfl-optimization-id`,
`data-ctfl-optimization-context-id`, and `data-ctfl-variant-index` are read when an event fires, so
updated values can affect later payloads. Per-element tracking overrides such as
`data-ctfl-track-views` are resolved when the element is added to the detector. If an existing
mounted element needs dynamic non-entry-ID override changes, use the
`tracking.*Element(...)` API or remount the tracked element.

## View tracking mechanics

View tracking uses `IntersectionObserver` and a qualification dwell timer.

View timing uses these fixed values:

| Behavior                    | Value   |
| --------------------------- | ------- |
| Required visible dwell time | 1000 ms |
| Minimum visible ratio       | 10%     |

The `IntersectionObserver` root defaults to the viewport, its root margin defaults to `0px`, and
the observer sweeps disconnected elements every 30000 ms. Applications can choose the root and root
margin, but cannot override the timing or visibility values.

A view session works like this:

1. The element crosses the fixed 10% visibility threshold.
2. The observer starts a fresh session, assigns a `viewId`, resets accumulated duration, and starts
   the dwell timer.
3. After the dwell time passes, the observer calls `trackView()` once to mark the session start,
   with `viewId` and the accumulated `viewDurationMs`.
4. While the element remains visible, the observer continues measuring duration without scheduling
   periodic interaction timers or emissions.
5. When the element leaves view after the session started, the observer calls `trackView()` once
   more to mark the session end, using the same `viewId` and the final `viewDurationMs`.
6. If the element leaves view before dwell time completes, the session resets without sending an
   event.

The observer ends and resets an active view session when the page becomes hidden or receives a
`pagehide` or `beforeunload` event. A qualified session gets its end interaction; a sub-dwell
session emits nothing. If the page becomes visible again and the element still meets the visibility
threshold, the observer starts a fresh session with a new `viewId` and dwell period instead of
resuming the ended session.

The observer coalesces in-flight callbacks so a slow or failing event send does not create duplicate
concurrent sends for the same element. If visibility or page lifecycle ends while the start event is
in flight, the end event is queued after that in-flight attempt settles.

Sticky view handling is per DOM element. If the payload has `sticky: true`, the first view attempt
for that element sends `sticky: true` to Core. After Core accepts that sticky view, later view
events for the same element omit `sticky`. If the sticky attempt is blocked, the detector retries
sticky on the next visibility session for that element. Separately rendered elements with the same
entry ID are treated as separate sticky targets.

### `display: contents` wrappers

React Web renders `OptimizedEntry` with `display: contents` by default so the wrapper can carry
tracking metadata without adding a layout box. Because the wrapper has no box of its own, view
tracking resolves what rendered area to measure.

When the wrapper has one rendered element child and no visible text, the view detector observes that
child element. When the wrapper contains multiple rendered boxes or visible text, the detector uses
virtual measurement of the wrapper contents against the viewport and clipping ancestors. The
detector remeasures when the rendered subtree, `class`, `hidden`, or `style` changes, and when
resize or scroll can change virtual visibility.

## Click tracking mechanics

Click tracking uses one document-level capture listener. It does not call `preventDefault()` or
`stopPropagation()`.

When a click occurs, the detector resolves two facts:

* Which tracked entry element owns the click.
* Whether the event path is semantically clickable.

The click detector treats these paths as clickable:

* `a[href]`
* `button`
* `input` except `type="hidden"`
* `select`
* `textarea`
* `summary`
* `[role="button"]`
* `[role="link"]`
* `[onclick]`
* elements with an `onclick` property handler
* `[data-ctfl-clickable="true"]`

The tracked entry can be the clicked element, an ancestor of the clicked element, or a descendant of
a clickable ancestor. The detector also handles click events whose original target is a text node by
walking to the parent element.

If a tracked entry and a clickable path are found, the detector resolves entry metadata and calls
`trackClick()`. Click events do not include a click duration or click ID; the payload carries the
entry and optimization metadata.

## Hover tracking mechanics

Hover tracking uses element-level listeners. When `PointerEvent` is available, the observer listens
for `pointerenter`, `pointerleave`, and `pointercancel`, and ignores touch pointer events. When
pointer events are unavailable, it falls back to `mouseenter` and `mouseleave`.

Hover tracking uses a fixed 1000 ms dwell time.

The observer sweeps disconnected elements every 30000 ms. Applications cannot override the hover
timing values.

A hover session mirrors the view session:

1. The pointer enters a tracked element.
2. The observer starts a fresh session, assigns a `hoverId`, resets accumulated duration, and starts
   the dwell timer.
3. After dwell time passes, the observer calls `trackHover()` once to mark the session start, with
   `hoverId` and the accumulated `hoverDurationMs`.
4. While the pointer remains hovered, the observer continues measuring duration without scheduling
   periodic interaction timers or emissions.
5. When the pointer leaves after the session started, the observer calls `trackHover()` once more to
   mark the session end, using the same `hoverId` and the final `hoverDurationMs`.
6. If hover ends before dwell time completes, the session resets without sending an event.

Like view tracking, hover tracking ends and resets its active session when the page becomes hidden
or receives `pagehide` or `beforeunload`. Restoring the visible page does not resume the ended hover
or infer that the pointer is still present. A new hover session needs a fresh pointer or mouse entry.
The observer also coalesces in-flight callbacks and sweeps disconnected element state.

## React Web mechanics

React Web wraps the Web SDK; it does not replace the Web SDK tracking runtime.

`OptimizationRoot` creates one `ContentfulOptimization` instance from its props and destroys that
instance on unmount. `trackEntryInteraction` maps to the lower-level Web SDK's
`autoTrackEntryInteraction` option. `allowedEventTypes`, `defaults`, `queuePolicy`, and other Web
SDK configuration values pass through to the underlying instance.

`OptimizedEntry` does three tracking-related things:

* Resolves a provided baseline entry, or fetches the baseline first when `OptimizedEntry` or
  `useOptimizedEntry()` receives an `entryId` and the SDK is configured with a `contentful.js`
  client.
* Renders a wrapper element with `display: contents`.
* Adds tracking attributes for the resolved entry when resolved content is ready.

The wrapper receives:

```html
<div
  data-ctfl-baseline-id="4ib0hsHWoSOnCVdDkizE8d"
  data-ctfl-entry-id="4k6ZyFQnR2POY5IJLLlJRb"
  data-ctfl-optimization-id="6IueRX1pS3iMJncbhUQTba"
  data-ctfl-optimization-context-id="e77eab64-93ca-4f6e-8492-037c1ff67caa"
  data-ctfl-sticky="true"
  data-ctfl-variant-index="1"
  data-ctfl-duplication-scope="session"
></div>
```

`data-ctfl-duplication-scope` is emitted by React Web for optimization metadata, but the Web SDK
entry interaction payload does not use it.

Managed entry fetching expects the same single-locale CDA entry shape as manual `baselineEntry`
resolution. Do not use `withAllLocales` or `locale=*` for Web or React Web optimization surfaces.

During loading, `OptimizedEntry` does not emit resolved entry tracking attributes, even when
`preserve-server` hydration keeps server-rendered content visible while state settles. Loading UI is
therefore not tracked as the resolved Contentful entry. If `children` is a direct `ReactNode`
instead of a render prop, the wrapper still receives tracking attributes after resolution, but the
child content does not change based on the resolved entry.

`useOptimizedEntry()` only resolves data. It does not add DOM attributes or register an element. If
a component uses `useOptimizedEntry()` directly, it must either render the `data-ctfl-*` attributes
itself or use `sdk.tracking.enableElement(...)`.

> **Warning**
>
> `OptimizedEntrySourceController` does not emit Web tracking attributes. Custom Web adapters that
> use it must render `data-ctfl-*` attributes after resolution or call
> `optimization.tracking.enableElement(...)` with equivalent data.

React Web router adapters emit `page()` calls when supported routers change route. They are page
event helpers, not entry interaction detectors. Entry views, clicks, and hovers still come from the
Web SDK runtime.

`OptimizationRoot` and `OptimizationAnalyticsRoot` use handoff `initialPageEvent` ownership for the
first browser route. When an analytics-only handoff skips that route, React StrictMode effect replay
does not emit a duplicate page event; later route-key changes still emit through the analytics
runtime. If the analytics root unmounts or a newer hydration starts before async hydration finishes,
the stale hydration stops before state apply, warning, or page tracking. Profileless static or
public analytics handoffs hydrate live tracking state without overwriting durable browser
continuity.

## Delivery and flushing

Insights events are queued by current profile ID and sent in batches. The queue flushes
periodically, flushes when the queued event count reaches the batch threshold, and can be flushed
explicitly with `optimization.flush()`.

Experience events are sent immediately when the browser is online. When the browser is offline,
Experience events are queued up to the configured offline maximum and replayed when the online
signal becomes `true`.

The Web SDK wires browser lifecycle events into this queue model:

* `online` and `offline` update Core's online signal. Going online forces a flush.
* A hidden `visibilitychange`, `pagehide`, or `beforeunload` ends active qualified view and hover
  sessions first so their end interactions can enter the Insights queue. The SDK then makes a
  best-effort `navigator.sendBeacon()` flush once per hide cycle. Browser shutdown can still end
  before delivery completes.
* Normal periodic, threshold, online, and explicit flushes use fetch so retry and failure policy can
  observe the response. This periodic queue delivery is separate from interaction session timing;
  active view and hover sessions do not emit periodic duration updates.

Browser storage writes are best-effort. If a `localStorage` write fails, live SDK state continues in
memory for the current runtime while durable continuity is limited. At startup, the SDK reads
consent from `localStorage` and resolves configured defaults with any profile-continuity values it
is allowed to load. Profile-continuity values such as profile, selected optimizations, Custom Flag
changes, and anonymous ID are read only when persistence consent permits it. The anonymous ID is
also persisted in the `ctfl-opt-aid` cookie and migrated from the legacy anonymous ID cookie when
profile continuity is enabled.

## Debugging model

When an expected interaction does not appear, check the gates in this order:

1. **Profile** - Call `page()` or `identify()` before relying on Insights events. Insights delivery
   needs a current profile in state.
2. **Consent** - Confirm `states.consent.current === true` or configure `allowedEventTypes` for the
   event types that must emit before consent.
3. **Detector startup** - Confirm React Web `trackEntryInteraction`, lower-level Web SDK
   `autoTrackEntryInteraction`, `tracking.disable(...)`, or `data-ctfl-track-*="false"` has not
   opted the relevant interaction out.
4. **Element metadata** - Confirm the tracked element is an HTML or SVG element with non-empty
   `data-ctfl-entry-id`, or that `enableElement(...)` supplies valid `data.entryId`.
5. **View threshold** - Confirm the element remains at least 10% visible for 1000 ms.
6. **Clickability** - Confirm clicks happen on a semantic clickable path or an element marked with
   `data-ctfl-clickable="true"`.
7. **Hover source** - Confirm the event is not a touch pointer event and that the pointer remains
   over the element long enough to satisfy dwell time.
8. **Manual override precedence** - Confirm `disableElement(...)` or `data-ctfl-track-*="false"` is
   not suppressing the element.

For local diagnostics, subscribe to `states.eventStream` and `states.blockedEventStream`, and use
`onEventBlocked` for consent-gating visibility. In React Web, use `onStatesReady` on
`OptimizationRoot` when those subscribers must be attached as soon as SDK state exists and before
provider children can emit router `page()` events or entry interactions.

## Design boundaries

The Web SDKs do not own every part of tracking:

* They do not infer Contentful entries from tracking metadata. Managed entry fetching requires an
  explicitly configured `contentful.js` client.
* They do not decide whether a user has granted consent.
* They do not infer a browser view from server rendering alone.
* They do not make non-clickable markup clickable.
* They do not replay individual event payloads blocked before consent.
* They do not guarantee persistence in browsers that deny storage access.

Keep these boundaries explicit when integrating or changing tracking behavior. Detection belongs to
the browser runtime, event semantics belong to Core, and application-specific policy stays in the
application.

## Related documentation

* [Core state management](/personalization/optimization-sdk/core-state-management/) - Core state, consent, queues, observables,
  and event streams.
* [Entry optimization and variant resolution](/personalization/optimization-sdk/entry-personalization-and-variant-resolution/) -
  How resolved entries and selected optimization metadata are produced before tracking metadata is
  rendered.
* [Interaction tracking in Node and stateless environments](/personalization/optimization-sdk/interaction-tracking-in-node-and-stateless-environments/) -
  How browser tracking fits when server runtimes own personalization.
* [Optimization Web SDK README](https://github.com/contentful/optimization/blob/optimization-swift-v2.0.1/packages/web/web-sdk/README.md) - Package-level orientation
  and Web SDK setup options.
* [Optimization React Web SDK README](https://github.com/contentful/optimization/blob/optimization-swift-v2.0.1/packages/web/frameworks/react-web-sdk/README.md) - React
  provider, hook, router, and entry-rendering orientation.
* [Integrate the Optimization Web SDK in a web app](/personalization/optimization-sdk/integrate-the-web-sdk-in-a-web-app/) -
  Step-by-step browser integration flow.
* [Integrate the Optimization React Web SDK in a React app](/personalization/optimization-sdk/integrate-the-react-web-sdk-in-a-react-app/) -
  Step-by-step React integration flow.
* [Build a custom JavaScript Optimization adapter](/personalization/optimization-sdk/build-a-custom-javascript-optimization-adapter/) -
  Low-level entry-source lifecycle guidance for custom adapter authors.
* [Forward Optimization SDK context to analytics and tag-management tools](/personalization/optimization-sdk/forwarding-optimization-sdk-context-to-analytics-and-tag-management-tools/) -
  Consent-aware forwarding, sticky-view dedupe, and Custom Flag analytics handoff.