Skip to Content
Delmare Digital

Proxy

createExperimentProxy, composing with your own proxy through resolveExperiments and applyExperiments, and what the proxy writes.

The proxy assigns versions on every eligible request and rewrites whole-page tests. Import from @delmaredigital/payload-experiments/proxy.

Standalone

src/proxy.ts
import { createExperimentProxy } from '@delmaredigital/payload-experiments/proxy'
import { proxyRuntime } from '@/experiments/proxyRuntime'

export const proxy = createExperimentProxy(proxyRuntime)

Composing with your own proxy

Call resolveExperiments, forward its request headers on both the normal response and any rewrite, then call applyExperiments to attach cookies and headers:

src/proxy.ts
import { NextResponse, type NextRequest } from 'next/server'
import { resolveExperiments, applyExperiments } from '@delmaredigital/payload-experiments/proxy'
import { proxyRuntime } from '@/experiments/proxyRuntime'

export async function proxy(request: NextRequest) {
  // ...your own logic
  const result = await resolveExperiments(request, proxyRuntime)
  const options = { request: { headers: result.requestHeaders } }
  const response = result.rewrite
    ? NextResponse.rewrite(result.rewrite, options)
    : NextResponse.next(options)
  return applyExperiments(response, result)
}

result.rewrite is set when the path belongs to a whole-page test and the visitor's version lives at another address. The result type is exported as ExperimentsProxyResult.

What it does on each request

  1. Removes any inbound x-xp-* headers, before any skip check, so a browser cannot inject them.
  2. Skips admin, API, Next.js internal and asset paths.
  3. Reads consent, the signed xp_id cookie (or mints a new one), and getUnit.
  4. Assigns each running or decided test the request is eligible for.
  5. Writes signed request headers for server code:
    • x-xp-assignments: the assignments, at most 1 KB.
    • x-xp-request: method, path and search, and consent, at most 2048 bytes.
  6. For whole-page tests, returns a rewrite to the assigned page. Holdouts see the Original page.

A newly minted cookie is forwarded into the same request, so server code sees it immediately. Server readers verify both headers; unsigned or changed values are ignored and a safe fallback is used when context is missing.

Whole-page routing

  • Requests to any target path of a live page test are rewritten to the assigned group's page.
  • A live test targeting a path takes precedence over archived routing.
  • After a winner is made permanent, losing paths keep rewriting to the winner while the archived test record exists, until Release pages.
  • Decided winners remain subject to targeting.
  • Archived page rewrites serve shipped content by path alone and record no exposure.

Assignment and exposure exclusions

Staff, bots matched by the built-in user-agent list, preview renders and HEAD requests still receive assignments, but those assignments have exposable: false and no exposures are recorded. With cookie.consent returning false, nobody is enrolled; the proxy serves identity-free fallback content. See Assignment and targeting.

Edit this page on GitHub

On this page