@contentful/optimization-react-web

中文文档
2.1.3发布于 2026/11/06MITTypeScriptESM + CJS近 30 天下载 3944

React SDK for Contentful Optimization

原始使用文档
文档目录17 节
  1. Optimization React Web SDK
  2. Getting started
  3. When to use this package
  4. Common configuration
  5. Core workflows
  6. Consent
  7. Provider and hook access
  8. Provider-managed state subscriptions
  9. Work before the initial page decision
  10. Analytics-only handoff
  11. OptimizedEntry
  12. Entry interaction tracking
  13. Router page events
  14. Live updates and preview
  15. Web Components
  16. Development harness
  17. Related

Contentful Logo

Contentful Personalization & Analytics

Optimization React Web SDK

The Optimization React Web SDK provides React providers, hooks, router adapters, and entry-rendering primitives on top of the Optimization Web SDK. Use it when a React browser application must not manage the lower-level Web SDK instance, state subscriptions, entry resolution, and route tracking by hand.

If you are integrating a React application, start with Getting Started, then use Integrating the Optimization React Web SDK in a React app for the step-by-step flow. This README keeps the package orientation and common setup options close at hand; generated reference documentation remains the source of truth for exported API signatures.

[!IMPORTANT]

Upgrading from v1? Follow the v1-to-v2 migration guide before changing package versions.

Table of Contents

Getting started

Install using an NPM-compatible package manager, pnpm for example:

pnpm install @contentful/optimization-react-web

Add contentful too when OptimizationRoot will use your app-owned contentful.js client for managed entry fetching.

React and React DOM are application-owned peer dependencies. The SDK uses the React runtime already installed by your app instead of installing its own copy.

Mount OptimizationRoot once near the root of your React application:

import { OptimizationRoot } from '@contentful/optimization-react-web'

function App() {
  return (
    <OptimizationRoot spaceId="your-space-id" environment="master">
      <YourApp />
    </OptimizationRoot>
  )
}

For a single-locale app that fetches Contentful entries, pass the application locale to the SDK when Experience API responses and events need to use the same language:

<OptimizationRoot spaceId="your-space-id" environment="master" locale="en-US">
  <YourApp />
</OptimizationRoot>

When to use this package

Use @contentful/optimization-react-web for React browser applications that need provider-based SDK initialization, hooks, router page tracking, optimized entry rendering, automatic interaction tracking, and live update semantics. Use the lower-level Web SDK directly for non-React integrations or custom framework adapters.

Common configuration

OptimizationRoot accepts most Web SDK configuration props directly and adds React-specific props such as liveUpdates, onStatesReady, handoff, and hydration. The Web SDK autoTrackEntryInteraction option is exposed as the React trackEntryInteraction prop.

PropRequired?DefaultDescription
spaceIdYesN/AContentful Space identifier used for Experience and Insights API requests
environmentNo'master'Contentful environment identifier for Experience and Insights API requests
apiNoWeb SDK defaultsExperience API and Insights API endpoint and request options
appNoundefinedApplication metadata attached to outgoing event context
contentfulNoundefinedApp-owned contentful.js client, default query, and cache
localeNoundefinedSDK Experience API and default event locale
defaultsNoundefinedConfiguration/default state such as consent or persistence consent
handoffNoundefinedServer, static, or edge Optimization handoff to hydrate
hydrationNohandoff?.hydrationContent hydration presentation mode for optimized entries
prefetchManagedEntriesNoundefinedManaged entry descriptors to warm after the live SDK is ready
allowedEventTypesNo['identify', 'page']Event types allowed before consent is explicitly set
trackEntryInteractionNo{ views: true, clicks: true, hovers: true }Automatic entry interaction tracking for OptimizedEntry elements
cookieNo{ domain: undefined, expires: 365 }Anonymous ID cookie settings inherited from the Web SDK
beforeInitialPageNoundefinedOwned-root callback that completes before the initial page decision
liveUpdatesNofalseWhether OptimizedEntry components react continuously to SDK state
onStatesReadyNoundefinedProvider-managed app-level state subscription hook
queuePolicyNoSDK defaultsFlush retry behavior and offline queue bounds
logLevelNo'error'Minimum log level for the default console sink
onEventBlockedNoundefinedCallback invoked when consent or guard logic blocks an event

Use OptimizationProvider directly when an application or framework adapter needs direct provider control, including integrations that supply an SDK instance. Use it instead of OptimizationRoot, not inside OptimizationRoot: the root already composes the provider and live-updates provider. Nesting providers can create a second owned SDK instance or shadow the root context, including handoff entries.

<OptimizationProvider sdk={optimization}>
  <YourApp />
</OptimizationProvider>

Injected SDK instances render children on the initial render. When handoff is provided, that initial render uses a snapshot runtime while provider setup hydrates the injected SDK. The provider still leaves SDK teardown to the owner that created the instance.

For server-to-browser state handoff, pass the server, static, or edge OptimizationHandoff through handoff on OptimizationRoot or OptimizationProvider. Pass hydration on the same component when the route needs to override the handoff's content presentation mode. Keep defaults for configuration or default state such as consent policy:

<OptimizationRoot
  spaceId="your-space-id"
  defaults={{ consent: true }}
  environment="master"
  handoff={handoff}
>
  <YourApp />
</OptimizationRoot>

Pass api.location through the normal OptimizationRoot or owned OptimizationProvider configuration:

<OptimizationRoot spaceId="your-space-id" api={{ location: { countryCode: 'DE', city: 'Berlin' } }}>
  <YourApp />
</OptimizationRoot>

For an injected SDK, configure api.location when constructing that SDK. Location is initialization-scoped, not a reactive prop. Event context.location takes precedence over matching request fields. An empty api.location: {} supplies an explicit empty default instead of API geolocation; it does not clear an existing profile location or lock location across later requests. See Location inputs and profile updates.

For every Web SDK option that passes through this package, use the Web SDK README and generated reference documentation.

Choose the application Contentful locale in your router, i18n layer, or app configuration. Pass that value directly to Contentful CDA requests, and pass the same value to the provider locale prop when Experience API responses and event context need to use the same language. See Locale handling in the Optimization SDK Suite for the full locale model.

For SDK instances created from provider/root configuration, changing the locale prop calls sdk.setLocale() after initialization while the rest of the SDK config remains initialization-scoped. Locale updates do not fetch content or refresh profile state; trigger your app's normal page(), identify(), route loader, or CDA fetch flow when localized data needs to change.

Core workflows

Consent policy remains application-owned. For default-on application policies that do not render an end-user consent UI, seed accepted consent on OptimizationRoot:

<OptimizationRoot spaceId="your-space-id" defaults={{ consent: true }}>
  <YourApp />
</OptimizationRoot>

When application policy depends on user choice, leave defaults.consent unset and call setConsent() from useOptimizationActions() in the relevant control:

import { useOptimizationActions } from '@contentful/optimization-react-web'

function ConsentButton() {
  const { setConsent } = useOptimizationActions()
  return <button onClick={() => setConsent(true)}>Accept</button>
}

Boolean consent calls control both event emission and durable profile-continuity persistence by default. Use sdk.consent({ events: true, persistence: false }) when events are allowed but continuity needs to stay session-only. For cross-SDK consent guidance, see Consent management in the Optimization SDK Suite.

Provider and hook access

OptimizationRoot owns the Web SDK lifecycle. Provider-owned initialization runs after React commit, outside render. Children render against an initial snapshot runtime, then a layout-effect setup initializes the live SDK before the first visible paint in normal browser rendering. Optimized-entry hooks also attach their controller listener, adopt current options, and connect during the layout phase so loading or committed content settles before paint. With hydration="preserve-server", server-rendered content remains continuously visible while the entry adopts live SDK state; this uses the existing root and entry APIs.

Use the dedicated React SDK action hooks when components need common Optimization actions:

import { useOptimizationActions } from '@contentful/optimization-react-web'

function ProductCta() {
  const { trackEvent } = useOptimizationActions()

  return <button onClick={() => trackEvent({ event: 'purchase' })}>Buy now</button>
}

Use useOptimization() when a component needs direct access to the SDK instance itself, and prefer useOptimizationActions() when a component wants destructurable action methods such as trackEvent(), identifyUser(), trackPageView(), trackScreen(), flushEvents(), resetUser(), or setConsent().

Use dedicated state hooks such as useConsentState(), useProfileState(), and useSelectedOptimizationsState() when components need to render current SDK state. Prefer those hooks over subscribing to sdk.states.* directly from component effects.

Use useEntryResolver() when a component needs manual entry resolution without the OptimizedEntry wrapper:

useOptimization() returns the SDK instance itself. Keep that instance in a variable and call methods from it. Do not destructure SDK methods from the returned value because those methods rely on the instance this binding.

import { useOptimization } from '@contentful/optimization-react-web'

function ProductCta() {
  const optimization = useOptimization()

  return <button onClick={() => optimization.track({ event: 'purchase' })}>Buy now</button>
}

The direct SDK surface also exposes manual interaction calls such as trackView(), trackClick(), trackHover(), and trackFlagView().

// Avoid destructuring SDK methods; this loses the instance binding.
const { track } = useOptimization()
import { useEntryResolver } from '@contentful/optimization-react-web'

function HeroEntry({ baselineEntry }) {
  const { resolveEntryData } = useEntryResolver()
  const resolvedData = resolveEntryData(baselineEntry)

  if (resolvedData.isEmptyVariant) return null
  return <HeroCard entry={resolvedData.entry} />
}

resolveEntry() remains available when code needs only the resolved entry. It does not expose empty-variant state, so use resolveEntryData() or resolveOptimizedEntry() for rendering decisions.

For manual entries, fetch Contentful entries in the app layer with one CDA locale before passing them to baselineEntry surfaces. For managed fetching, use entryId with optional entryQuery, or pass a managedEntry object with contentType, slug, optional slugField, and optional entryQuery. slugField defaults to slug. Do not pass all-locale CDA responses from withAllLocales or locale=*; these APIs expect direct single-locale field values. See Entry optimization and variant resolution for the entry contract and Locale handling in the Optimization SDK Suite for the broader locale model.

For optimized entry content, prefer the OptimizedEntry render context and pass getMergeTagValue into the child renderer:

import { OptimizedEntry } from '@contentful/optimization-react-web'

function HeroEntry({ baselineEntry }) {
  return (
    <OptimizedEntry baselineEntry={baselineEntry}>
      {(resolvedEntry, { getMergeTagValue }) => (
        <HeroCard entry={resolvedEntry} getMergeTagValue={getMergeTagValue} />
      )}
    </OptimizedEntry>
  )
}

Use useMergeTagResolver() for components that resolve merge tags outside an OptimizedEntry render prop.

If a merge tag references localized profile fields such as location.city or location.country, its resolved value follows the localized profile values returned by the Experience API.

Provider-managed state subscriptions

Use onStatesReady when application code needs to subscribe to SDK state as part of provider initialization. This avoids coordinating with window.contentfulOptimization, which might not exist yet when application code runs or might have already emitted data by the time a later effect subscribes.

<OptimizationRoot
  spaceId="your-space-id"
  onStatesReady={(states) => {
    const subscriptions = [
      states.eventStream.subscribe((event) => {
        if (event) devToolsPanel.logEvent(event)
      }),
      states.blockedEventStream.subscribe((blocked) => {
        if (blocked) devToolsPanel.logBlockedEvent(blocked)
      }),
    ]

    return () => {
      subscriptions.forEach((subscription) => subscription.unsubscribe())
    }
  }}
>
  <YourApp />
</OptimizationRoot>

The callback receives only sdk.states. It runs when layout-effect provider setup initializes the live state surface. Initial children can already render against a snapshot runtime, and subscriptions can still observe events emitted by child effects such as router page tracking. For component-local UI state, keep using hooks and React effects under the provider.

Work before the initial page decision

Use beforeInitialPage on an owned OptimizationRoot when browser identity or custom Experience event work must finish before that root's initial page decision. The initial page decision is the root's one choice to send the first browser page event or skip it because an applied handoff already owns that route. After the root's live owned runtime exists, the callback receives receiver-safe identify, screen, and track methods, so you can destructure and call them without losing the SDK receiver:

<OptimizationRoot
  spaceId="your-space-id"
  environment="master"
  routeKey={routeKey}
  buildPagePayload={() => ({ properties: { route: routeKey } })}
  beforeInitialPage={{
    run: async ({ identify }) => {
      await identify({ userId: visitor.id })
    },
    onError: reportBeforeInitialPageError,
  }}
>
  <YourApp />
</OptimizationRoot>

A root with beforeInitialPage requires routeKey and lazy buildPagePayload, and it does not accept initialPagePayload. The app owns routeKey as the stable identity of the current route and owns buildPagePayload as a lazy read of current page data. The root awaits the work returned by run or its watchdog, reads the latest route and payload builder for one direct page attempt, activates the existing page emitter with a non-emitting initial skip mark for the attempted route, and emits normally for later route changes. A successfully applied same-route handoff can make the direct page decision a skip; otherwise, the direct page attempt uses emit.

This root is the sole page owner for its subtree. Do not also mount a React Router, TanStack Router, Next.js App Router, or Next.js Pages Router automatic page tracker. Omit beforeInitialPage when a separate router tracker owns page events. The option is available only to the owned content root; OptimizationProvider, injected providers, and OptimizationAnalyticsRoot do not accept it.

When maxWaitMs is omitted, it defaults to 3,000 ms. It accepts positive finite values. 0, negative values, NaN, Infinity, and -Infinity synchronously throw TypeError('beforeInitialPage.maxWaitMs must be a positive finite number.') before the provider, callback, page, or onError runs.

The sequence is best-effort. Return every promise or thenable that must finish before page; fire-and-forget work continues as later activity. Callback rejection or watchdog expiry is reported through onError when supplied. While the root remains mounted and the same live owned runtime is current, the page is still attempted. The watchdog stops waiting but does not cancel callback code or an in-flight request. If the root unmounts or its runtime is replaced, only unsent local page and readiness continuation is suppressed.

A route change after the direct page attempt starts neither cancels that attempt nor starts a competing attempt. The root settles and marks the captured attempted route before enabling later page emission. A route observed only during the in-flight attempt is not emitted; a route change after readiness emits normally. The existing optimized-entry deadline can commit baseline content first, and with default liveUpdates={false}, that fallback remains frozen after the before-initial-page work later succeeds.

Analytics-only handoff

Use OptimizationAnalyticsRoot when a route already rendered optimized content on the server, during static generation, ISR, or at the edge, and the browser only needs to hydrate analytics, page tracking, and entry interaction tracking. The handoff must use hydration: 'analytics-only'; content-capable handoffs still belong on OptimizationRoot or OptimizationProvider.

import { OptimizationAnalyticsRoot } from '@contentful/optimization-react-web'

function App({ handoff, routeKey }) {
  return (
    <OptimizationAnalyticsRoot
      spaceId="your-space-id"
      environment="master"
      handoff={handoff}
      routeKey={routeKey}
    >
      <YourApp />
    </OptimizationAnalyticsRoot>
  )
}

Pass initialPagePayload or buildPagePayload when an initial browser page event needs route payload fields beyond the default route identity.

OptimizedEntry

OptimizedEntry fetches a Contentful entry by ID or by a managedEntry descriptor when the root SDK is configured with contentful: { client }, then renders the selected variant or baseline. baselineEntry remains supported for the manual path:

import { OptimizedEntry } from '@contentful/optimization-react-web'

function HeroEntry({ baselineEntry }) {
  return (
    <OptimizedEntry baselineEntry={baselineEntry}>
      {(resolvedEntry, { getMergeTagValue }) => (
        <HeroCard entry={resolvedEntry} getMergeTagValue={getMergeTagValue} />
      )}
    </OptimizedEntry>
  )
}

To model baseline and variant entries with different content types, see Entry optimization and variant resolution.

When OptimizationRoot uses a Web SDK configured with contentful: { client }, React entry surfaces can fetch by content type and slug through managedEntry. slugField is optional when the field is named slug:

function HeroEntry() {
  return (
    <OptimizedEntry
      managedEntry={{
        contentType: 'page',
        slug: 'home',
        entryQuery: { locale: 'en-US' },
      }}
      loadingFallback={() => <HeroSkeleton />}
      errorFallback={() => <HeroFallback />}
    >
      {(resolvedEntry) => <HeroCard entry={resolvedEntry} />}
    </OptimizedEntry>
  )
}

Use prefetchManagedEntries on OptimizationRoot or OptimizationProvider to warm the client-side managed entry cache after the live SDK is ready. For SSR handoff, put ManagedEntryHandoff[] values in handoff.entries; prefetchManagedEntries(runtime, descriptors) returns that shape when the runtime is available on the server. Slug handoffs nest the normalized descriptor under managedEntry and retain the fetched entry's sys.id in entryId, so matching browser slug sources hydrate without another fetch.

Hooks use the same managed entry source:

import { useOptimizedEntry } from '@contentful/optimization-react-web'

function HeroEntry() {
  const { entry, error, isLoading } = useOptimizedEntry({
    managedEntry: {
      contentType: 'page',
      slug: 'home',
      entryQuery: { locale: 'en-US' },
    },
  })

  if (isLoading) return <HeroSkeleton />
  if (error || !entry) return <HeroFallback />
  return <HeroCard entry={entry} />
}

baselineEntry remains supported and takes the manual path. Use onEntryError when application code needs to log or report managed CDA failures. Use errorFallback on OptimizedEntry to render fallback UI for managed CDA failures. Use onEntryResolved, the render prop metadata, or the hook's metadata and isResolved fields when application code needs the baseline ID, resolved entry ID, or optimization context after tracking attributes are ready. Successful slug lookups report and track the fetched entry's sys.id, not the slug.

Use loadingFallback, direct children, wrapper props, and nested composition patterns when needed. For optimized entries, the loading phase begins immediately while optimization is unresolved. If the state is still unresolved after 5 seconds, the component reveals baseline content so loading does not persist forever. Without a custom loadingFallback, the wrapper preserves layout by hiding the baseline until that timeout elapses. That baseline is the first content shown for its baseline entry ID, so later pending or failed state cannot restore loading. The React Web guide covers those variants in context.

Entry interaction tracking

OptimizedEntry emits the Web SDK's data-ctfl-* tracking attributes for resolved entries. The root config observes views, clicks, and hovers by default; pass false for any interaction type that your application does not want to observe:

<OptimizationRoot spaceId="your-space-id" trackEntryInteraction={{ hovers: false }}>
  <YourApp />
</OptimizationRoot>

Use OptimizedEntry props to configure Web SDK entry-tracking attributes without setting data-ctfl-* metadata manually:

<OptimizedEntry baselineEntry={entry} clickable trackHovers={false}>
  {(resolvedEntry) => <HeroCard entry={resolvedEntry} />}
</OptimizedEntry>

OptimizedEntry derives entry ID, baseline ID, optimization ID, optimization context ID, sticky state, variant index, and duplication scope from the resolved entry state. View and hover timing use the Web SDK's fixed thresholds and intervals.

Use sdk.tracking.enableElement(...) from useOptimization() for manual element overrides.

Router page events

Router adapters emit page() events for supported client-side routers:

RouterImport pathMounting rule
React Router@contentful/optimization-react-web/router/react-routerMount under a React Router data router and inside OptimizationRoot
Next.js Pages@contentful/optimization-react-web/router/next-pagesMount once in pages/_app.tsx inside OptimizationRoot
Next.js App Router@contentful/optimization-react-web/router/next-appMount in app/layout.tsx inside OptimizationRoot
TanStack Router@contentful/optimization-react-web/router/tanstack-routerMount under the TanStack router tree and inside OptimizationRoot

The next-pages tracker remains available for low-level Pages Router wiring. For full Next.js Pages Router SSR setup with getServerSideProps, request handoff, and anonymous ID cookie writes, prefer the @contentful/optimization-nextjs/pages-router adapter path.

All adapters support static and dynamic page payload enrichment. See the React Web integration guide for router-specific examples.

Live updates and preview

liveUpdates defaults to false, so optimized entries keep the first content shown for their baseline entry ID. Set liveUpdates globally or per OptimizedEntry when entries must react to profile, flag, or preview changes:

<OptimizationRoot spaceId="your-space-id" liveUpdates={true}>
  <OptimizedEntry baselineEntry={entry} liveUpdates={false}>
    {(resolvedEntry) => <Card entry={resolvedEntry} />}
  </OptimizedEntry>
</OptimizationRoot>

The browser preview panel is provided by @contentful/optimization-web-preview-panel. When the panel is open, live updates are forced on for all OptimizedEntry components so authors can inspect variant changes immediately.

Web Components

The React SDK keeps rendering React components. OptimizedEntry does not render through custom elements, and this package does not import or register Web Components.

Use the optional @contentful/optimization-web/web-components subpath only when a non-React app or a deliberate custom-element island needs vanilla custom elements. Framework wrappers around those elements must assign complex DOM properties such as baselineEntry, defaults, api, sdk, and callbacks after hydration, and listen for entry lifecycle events instead of trying to emulate React render props. See the Web SDK README for raw custom-element and UMD usage.

Development harness

The package-local development harness runs from packages/web/frameworks/react-web-sdk/dev/. Launch it from the repo root:

pnpm --filter @contentful/optimization-react-web dev:launch

Use the harness for package development. Use the reference implementation for end-to-end integration behavior.

版本与发布记录

@contentful/optimization-react-web 共发布 19 个版本,首次发布于 2026/04/02;过去 12 个月发布了 19 个版本。

版本发布日期距今
2.1.3latest2026/11/064 天前
2.1.12026/10/2220 天前
2.1.02026/10/1922 天前
2.0.22026/10/1625 天前
2.0.12026/09/231 个月前
2.0.02026/09/211 个月前

关于 @contentful/optimization-react-web 的常见问题

@contentful/optimization-react-web 是什么?

React SDK for Contentful Optimization

如何安装 @contentful/optimization-react-web?

在项目目录运行 npm install @contentful/optimization-react-web,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.3.1)、react-dom(>=18.3.1)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@contentful/optimization-react-web 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@contentful/optimization-react-web 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@contentful/optimization-react-web 可以商用吗?

@contentful/optimization-react-web 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@contentful/optimization-react-web 的下载量有多少?

根据 npm 官方下载统计,@contentful/optimization-react-web 在 2026/10/11 – 2026/11/09 这 30 天内被下载 3,944 次,日均约 131 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@contentful/optimization-react-web 还在维护吗?

最新版本 2.1.3 发布于 2026/11/06(4 天前);至今共发布 19 个版本,其中过去 12 个月发布了 19 个;首次发布于 2026/04/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。