Skip to Content
Delmare Digital

Client counting

Mount ExperimentsProvider for viewport blocks, whole-page pageviews, click goals and client-side flag reads.

Some exposures and conversions happen in the browser: a block scrolling into view, a page appearing after navigation, a click, a client component reading a flag point. These need an ExperimentsProvider with a value built on the server.

Mount the provider

src/components/ExperimentsLayer.tsx
import { ExperimentsProvider } from '@delmaredigital/payload-experiments/client'
import { getAssignments, toClientAssignments } from '@delmaredigital/payload-experiments/rsc'
import { serverRuntime } from '@/experiments/serverRuntime'

export async function ExperimentsLayer({ pathname, children }: { pathname: string; children: React.ReactNode }) {
  const assignments = await getAssignments(serverRuntime)
  return (
    <ExperimentsProvider value={toClientAssignments(assignments, { pathname })}>
      {children}
    </ExperimentsProvider>
  )
}

toClientAssignments(assignments, { pathname, apiRoute?, flagPoints?, experiments? }) builds a value with no visitor identifiers.

OptionPurpose
pathnameThe served page's own pathname, including after a rewrite
apiRouteYour Payload API base, if not /api. Use one value for every provider on a page
flagPointsFlag point ids client components may read with useFlagPoint. Default none
experimentsTest short names client components may read with useExperiment. Default none

Both allow-lists are empty by default, so the browser only learns about the tests it needs. Like getAssignments, render this inside <Suspense> and outside 'use cache'.

What needs a provider

FeatureWhat to mount
Puck viewport blocksA provider on every page that renders them. A layout-level provider with empty allow-lists is enough
Payload-blocks viewport groupsA provider, plus ExperimentGroup around each group (Payload blocks)
Whole-page testsOn each target page, a nested provider built for that page's pathname, with an ExperimentPageview component inside
Click goalsAny provider around the content; click recording is armed when exposure recording is not suppressed and at least one assignment is exposable, regardless of that test's goals
useFlagPoint, useExperimentA provider whose allow-list includes the id or short name
trackConversionBeaconA provider; without one it sends nothing

Without a provider, viewport exposures, pageviews and clicks cannot be sent.

Pageviews for whole-page tests

On each whole-page test target
import { ExperimentsProvider, ExperimentPageview } from '@delmaredigital/payload-experiments/client'
import { toClientAssignments } from '@delmaredigital/payload-experiments/rsc'

<ExperimentsProvider value={toClientAssignments(assignments, { pathname })}>
  <ExperimentPageview />
  {children}
</ExperimentsProvider>

ExperimentPageview reads the nearest provider and records the page that appeared, including after client navigation.

Client hooks

useFlagPoint(id) and useExperiment(slug) return the current group and settings with an expose(renderedArm) function. Neither records anything on its own: call expose with the group you actually rendered, once it shows. expose only sends for tests that use the manual policy.

An id or short name missing from the allow-list throws in development and returns the Original with one console error in production.

Client conversions

'use client'
import { trackConversionBeacon } from '@delmaredigital/payload-experiments/client'

trackConversionBeacon('newsletter_signup', { idempotencyKey: `newsletter:${formId}` })

The server accepts it only for an event source that is client-writable. Pass a stable key for conversions that matter, so retries count as one.

Event limits

Browser events go to POST /xp/events. A request needs a valid signed visitor cookie, its body is limited to 8192 bytes by default (events.maxBodyBytes) and to 20 events (MAX_EVENTS_PER_BODY), and your events.rateLimit adapter runs on it. Accepted and dropped events both return 202 {}. See Security.

Edit this page on GitHub

On this page