npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@contentful/optimization-react-web

v0.1.0-alpha12

Published

<p align="center"> <a href="https://www.contentful.com/developers/docs/personalization/"> <img alt="Contentful Logo" title="Contentful" src="https://raw.githubusercontent.com/contentful/optimization/v0.1.0-alpha12/contentful-icon.png" width="150">

Downloads

260

Readme

Guides · Reference · Contributing

[!WARNING]

The Optimization SDK Suite is pre-release (alpha). Breaking changes may be published at any time.

React Web SDK package for @contentful/optimization-react-web.

Status

Core root/provider primitives and React-facing APIs are implemented.

  • OptimizationProvider + useOptimization() context behavior
  • useOptimizationContext() readiness/error access
  • LiveUpdatesProvider + useLiveUpdates() global live updates context
  • OptimizationRoot provider composition and defaults
  • useOptimizedEntry() imperative optimization resolution
  • OptimizedEntry entry resolution, lock/live-update behavior, loading fallback, and data-attribute mapping

Purpose

@contentful/optimization-react-web is intended to become the React framework layer on top of @contentful/optimization-web.

Development

From repository root:

pnpm --filter @contentful/optimization-react-web build
pnpm --filter @contentful/optimization-react-web typecheck
pnpm --filter @contentful/optimization-react-web test:unit
pnpm --filter @contentful/optimization-react-web dev

From this package directory:

pnpm build
pnpm typecheck
pnpm test:unit
pnpm dev

Current Contents

  • package metadata and dual module exports
  • rslib/rsbuild/rstest/TypeScript baseline aligned with Web SDK patterns
  • core provider/root/context primitives in src/
  • OptimizedEntry component with loading-state support and Web SDK data-attribute tracking
  • scaffold dev dashboard harness with the host shell in dev/ and the React app in dev/app/ for consent, identify/reset, state, events, and entries

Usage

Initialization

Pass configuration props directly to OptimizationRoot (recommended) or OptimizationProvider. The SDK is initialized internally by the provider. OptimizationProvider can also receive a prebuilt sdk instance when ownership needs to stay outside React.

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

function App() {
  return (
    <OptimizationRoot
      clientId="your-client-id"
      environment="main"
      api={{
        insightsBaseUrl: 'https://ingest.insights.ninetailed.co/',
        experienceBaseUrl: 'https://experience.ninetailed.co/',
      }}
      liveUpdates={true}
    >
      <YourApp />
    </OptimizationRoot>
  )
}

Available config props:

| Prop | Type | Required | Description | | --------------------------- | ---------------------------------- | -------- | ----------------------------------------------------- | | clientId | string | Yes | Your Contentful Optimization client identifier | | environment | string | No | Contentful environment (defaults to 'main') | | api | CoreApiConfig | No | Unified Experience API and Insights API configuration | | app | App | No | Application metadata for events | | autoTrackEntryInteraction | AutoTrackEntryInteractionOptions | No | Automatic entry interaction tracking options | | logLevel | LogLevels | No | Minimum log level for console output | | liveUpdates | boolean | No | Enable global live updates (defaults to false) |

Provider Composition

OptimizationRoot composition order:

  1. OptimizationProvider (outermost)
  2. LiveUpdatesProvider
  3. application children

Hooks

  • useOptimization() returns the initialized ContentfulOptimization instance.
  • useOptimizationContext() returns { sdk, isReady, error } without requiring readiness.
  • useOptimizedEntry({ baselineEntry, liveUpdates }) returns resolved entry data and optimization state for imperative consumers.
  • useOptimization() throws if used outside OptimizationProvider.
  • useOptimization() also throws if the provider exists but the SDK is not ready.
  • useLiveUpdates() throws if used outside LiveUpdatesProvider.

Automatic Page Events

Router adapters are published as isolated subpath exports so applications can import only the router they use.

The Next.js Pages Router adapter:

import type { AppProps } from 'next/app'
import { OptimizationRoot } from '@contentful/optimization-react-web'
import { NextPagesAutoPageTracker } from '@contentful/optimization-react-web/router/next-pages'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <OptimizationRoot
      clientId="your-client-id"
      environment="main"
      api={{
        insightsBaseUrl: 'https://ingest.insights.ninetailed.co/',
        experienceBaseUrl: 'https://experience.ninetailed.co/',
      }}
    >
      <NextPagesAutoPageTracker />
      <Component {...pageProps} />
    </OptimizationRoot>
  )
}

Mount NextPagesAutoPageTracker once inside your provider tree, typically in pages/_app.tsx. The adapter waits for router.isReady, emits on the first eligible render, emits on route changes, and suppresses duplicate consecutive router.asPath values.

Page Payload Enrichment

Automatic page events can be enriched with static and dynamic payloads before calling optimization.page(...).

<NextPagesAutoPageTracker
  pagePayload={{
    properties: {
      appSection: 'storefront',
    },
  }}
  getPagePayload={({ context, isInitialEmission }) => ({
    locale: isInitialEmission ? 'en-US' : undefined,
    properties: {
      path: context.asPath,
      routePattern: context.pathname,
      slug: Array.isArray(context.query.slug) ? context.query.slug.join('/') : context.query.slug,
    },
  })}
/>
  • pagePayload is included in every auto-emitted page event.
  • getPagePayload runs once per emitted page event with route-aware context.
  • Static and dynamic payloads are merged before optimization.page(...) is called.
  • When the same field exists in both payloads, the dynamic payload wins.
  • This feature is implemented through page payload composition only; no interceptor setup is required or documented for it.

The package dev/ harness keeps the host HTML shell and rsbuild config at the top level, with the React app itself under dev/app/. It mounts the React Router adapter for interactive local verification. Other router adapters are still covered primarily through unit tests and the integration examples above.

The Next.js App Router adapter:

'use client'

import { OptimizationRoot } from '@contentful/optimization-react-web'
import { NextAppAutoPageTracker } from '@contentful/optimization-react-web/router/next-app'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <OptimizationRoot
      clientId="your-client-id"
      environment="main"
      api={{
        insightsBaseUrl: 'https://ingest.insights.ninetailed.co/',
        experienceBaseUrl: 'https://experience.ninetailed.co/',
      }}
    >
      <NextAppAutoPageTracker />
      {children}
    </OptimizationRoot>
  )
}

Mount NextAppAutoPageTracker once in a client component inside your App Router provider tree, typically via a providers.tsx wrapper used by app/layout.tsx. The adapter emits on the first eligible render and on pathname + search changes.

<NextAppAutoPageTracker
  pagePayload={{
    properties: {
      appSection: 'storefront',
    },
  }}
  getPagePayload={({ context, isInitialEmission }) => ({
    locale: isInitialEmission ? 'en-US' : undefined,
    properties: {
      path: context.url,
      pathname: context.pathname,
      search: context.search,
    },
  })}
/>

App Router payload enrichment follows the same payload-composition behavior as the Pages Router adapter and does not use interceptors.

The React Router adapter:

import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom'
import { OptimizationRoot } from '@contentful/optimization-react-web'
import { ReactRouterAutoPageTracker } from '@contentful/optimization-react-web/router/react-router'

export function AppLayout() {
  return (
    <OptimizationRoot
      clientId="your-client-id"
      environment="main"
      api={{
        insightsBaseUrl: 'https://ingest.insights.ninetailed.co/',
        experienceBaseUrl: 'https://experience.ninetailed.co/',
      }}
    >
      <ReactRouterAutoPageTracker />
      <Outlet />
    </OptimizationRoot>
  )
}

const router = createBrowserRouter([
  {
    path: '/',
    element: <AppLayout />,
    children: [
      {
        index: true,
        element: <HomePage />,
      },
    ],
  },
])

export function AppRouter() {
  return <RouterProvider router={router} />
}

Mount ReactRouterAutoPageTracker once inside the react-router-dom router tree and inside the optimization provider tree, typically in your root layout route. The adapter currently depends on useMatches(), so it must run under a React Router data router such as createBrowserRouter with RouterProvider, not a plain BrowserRouter. It emits on the first render and on pathname + search + hash changes.

<ReactRouterAutoPageTracker
  pagePayload={{
    properties: {
      appSection: 'storefront',
    },
  }}
  getPagePayload={({ context, isInitialEmission }) => ({
    locale: isInitialEmission ? 'en-US' : undefined,
    properties: {
      hash: context.hash,
      matchCount: context.matches.length,
      path: context.url,
      pathname: context.pathname,
    },
  })}
/>

React Router payload enrichment uses the same page-payload composition behavior and does not use interceptors.

The TanStack Router adapter:

import { Outlet } from '@tanstack/react-router'
import { OptimizationRoot } from '@contentful/optimization-react-web'
import { TanStackRouterAutoPageTracker } from '@contentful/optimization-react-web/router/tanstack-router'

export function RootLayout() {
  return (
    <OptimizationRoot
      clientId="your-client-id"
      environment="main"
      api={{
        insightsBaseUrl: 'https://ingest.insights.ninetailed.co/',
        experienceBaseUrl: 'https://experience.ninetailed.co/',
      }}
    >
      <TanStackRouterAutoPageTracker />
      <Outlet />
    </OptimizationRoot>
  )
}

Mount TanStackRouterAutoPageTracker once inside the TanStack router tree and inside the optimization provider tree, typically in your root route component. The adapter emits on the first render and on TanStack Router location.href changes.

<TanStackRouterAutoPageTracker
  pagePayload={{
    properties: {
      appSection: 'storefront',
    },
  }}
  getPagePayload={({ context, isInitialEmission }) => ({
    locale: isInitialEmission ? 'en-US' : undefined,
    properties: {
      hash: context.hash,
      matchCount: context.matches.length,
      path: context.url,
      pathname: context.pathname,
      search: context.search,
    },
  })}
/>

TanStack Router payload enrichment also uses page-payload composition only and does not require interceptors.

OptimizedEntry Component

import { OptimizedEntry } from '@contentful/optimization-react-web'
;<OptimizedEntry baselineEntry={baselineEntry}>
  {(resolvedEntry) => <HeroCard entry={resolvedEntry} />}
</OptimizedEntry>

OptimizedEntry behavior:

  • Default mode locks to the first non-undefined optimization state.
  • liveUpdates={true} enables continuous updates as optimization state changes.
  • If liveUpdates is omitted, global root liveUpdates is used.
  • If both are omitted, live updates default to false.
  • Consumer content supports render-prop ((resolvedEntry) => ReactNode) or direct ReactNode.
  • Wrapper element is configurable with as: 'div' | 'span' (defaults to div).
  • Wrapper style uses display: contents to remain layout-neutral as much as possible.
  • Readiness is inferred automatically:
    • optimized entries render when canOptimize === true
    • baseline entries render when the SDK instance is initialized

Loading Fallback

When loadingFallback is provided, it is rendered while readiness is unresolved.

<OptimizedEntry
  baselineEntry={baselineEntry}
  loadingFallback={() => <Skeleton label="Loading optimized content" />}
>
  {(resolvedEntry) => <HeroCard entry={resolvedEntry} />}
</OptimizedEntry>
  • If a baseline entry has optimization references and is unresolved, loading UI is rendered by default.
  • If the entry has no optimization references, baseline/resolved content is rendered directly.
  • During loading, a concrete layout-target element is rendered (data-ctfl-loading-layout-target) so loading visibility/layout behavior remains targetable even when wrapper uses display: contents.
  • During server rendering, unresolved loading is rendered invisibly (visibility: hidden) to preserve layout space before content is ready.

Nested Composition

Nested optimized entries are supported by explicit composition:

<OptimizedEntry baselineEntry={parentEntry}>
  {(resolvedParent) => (
    <ParentSection entry={resolvedParent}>
      <OptimizedEntry baselineEntry={childEntry}>
        {(resolvedChild) => <ChildSection entry={resolvedChild} />}
      </OptimizedEntry>
    </ParentSection>
  )}
</OptimizedEntry>

Nesting guard behavior:

  • Nested wrappers with the same baseline entry ID as an ancestor are invalid and are blocked.
  • Nested wrappers with different baseline entry IDs remain supported.

Auto-Tracking Data Attributes

When resolved content is rendered, the wrapper emits attributes used by @contentful/optimization-web automatic tracking:

  • data-ctfl-entry-id (always present on resolved content wrapper)
  • data-ctfl-optimization-id (when optimized)
  • data-ctfl-sticky (when available)
  • data-ctfl-variant-index (when optimized)
  • data-ctfl-duplication-scope (when available)

To consume those attributes automatically, enable Web SDK auto-tracking with one of:

  • autoTrackEntryInteraction: { views: true } during OptimizationRoot initialization
  • optimization.tracking.enable('views') / equivalent runtime setup APIs when applicable

When loadingFallback is shown, resolved-content tracking attributes are not emitted.

Live Updates Resolution Semantics

Consumers should resolve live updates behavior with:

const isLiveUpdatesEnabled =
  liveUpdatesContext.previewPanelVisible ||
  (componentLiveUpdates ?? liveUpdatesContext.globalLiveUpdates)

This gives:

  • preview panel open override first
  • component-level liveUpdates prop override first
  • then root-level liveUpdates
  • then default false

SDK Initialization Contract

  • Core/Web SDK initialization is synchronous; no dedicated sdkInitialized state is exposed.
  • React provider initialization outcome is represented by instance creation success/failure.
  • The async runtime path is preview panel lifecycle, already represented by preview panel state.

Migration Notes

  • OptimizedEntry now accepts either render-prop children or direct ReactNode children.
  • Entries with optimization references now render loading UI until optimization readiness is available.
  • When no loadingFallback is provided, a default loading UI is rendered for unresolved optimized entries.
  • Nested wrappers with the same baseline entry ID are now blocked at runtime.
  • Loading renders include data-ctfl-loading-layout-target for layout/visibility targeting.

Singleton Behavior

The underlying @contentful/optimization-web SDK enforces a singleton pattern. Only one ContentfulOptimization runtime can exist at a time (attached to window.contentfulOptimization). Attempting to initialize a second runtime will throw an error.

When using the config-as-props pattern, the provider uses a useRef to ensure the instance is only created once, even across React re-renders or StrictMode double-rendering.

Testing

When testing components that use the Optimization providers, pass test config props:

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

render(
  <OptimizationRoot
    clientId="test-client-id"
    environment="main"
    api={{
      insightsBaseUrl: 'http://localhost:8000/insights/',
      experienceBaseUrl: 'http://localhost:8000/experience/',
    }}
  >
    <ComponentUnderTest />
  </OptimizationRoot>,
)