• CMSnpm version

    • Overview
    • ContentBuilder
    • InviteUserForm
    • LoginForm
    • RenewPasswordForm
  • UInpm version

    • Overview
    • Accordion
    • AlertBubble
    • AnnouncementBar
    • Avatar
    • AvatarFileInput
    • Badge
    • Button
    • ButtonGroup
    • ButtonList
    • Calendar
    • Checkbox
    • CheckboxButton
    • CheckboxInput
    • CheckboxList
    • Chip
    • ColorRadio
    • ColorRadioGroup
    • Combobox
    • DatePicker
    • DatePickerInput
    • DateRangePicker
    • DateRangePickerInput
    • DatetimePicker
    • DatetimePickerInput
    • Dialog
    • Dropdown
    • Dropzone
    • ErrorMessage
    • FileInput
    • FlashMessages
    • FormComponent
    • Icon
    • IconButton
    • ImageGallery
    • InfoBox
    • Input
    • Label
    • Layout
    • Lightbox
    • ListItem
    • Loader
    • Lozenge
    • Menu
    • Message
    • Modal
    • ✅ ModalDialog
    • ✅ ModalHeader
    • MultiCombobox
    • MultiSelect
    • Pagination
    • Paper
    • Popover
    • Radio
    • RadioGroup
    • RasterImage
    • Select
    • ✅ Tabs
    • TextInput
    • TextLink
    • Textarea
    • TimePicker
    • TimePickerInput
    • Toggle
    • Tooltip
    • Typography
  • Formnpm version

    • Overview
    • AvatarFileInput
    • CheckboxButton
    • CheckboxInput
    • CheckboxList
    • ColorRadioGroup
    • Combobox
    • DatePickerInput
    • DateRangePickerInput
    • DatetimePickerInput
    • Dropzone
    • FileInput
    • Form
    • FormRenderer
    • GpsInput
    • MoneyInput
    • MultiCombobox
    • MultiSelect
    • NumberInput
    • PasswordInput
    • RadioGroup
    • Select
    • TextInput
    • Textarea
    • TimePickerInput
    • Toggle
  • DataGridnpm version

    • Overview
    • DataGrid
    • DataGridCustomExample
    • ExportButton
    • FilterList
    • Filters
    • FiltersButton
    • FulltextInput
    • HiddenColumns
    • HiddenColumnsButton
    • Pagination
    • RowCounts
    • RowsPerPageSelect
    • SelectedRowsToolbar
    • TableV2
    • ToolbarControl
    • ToolbarCustoms
    • ToolbarTabs
  • Wysiwygnpm version

    • Overview
  • Resizernpm version

    • Overview
  • Routernpm version

    • Overview
  • Corenpm version

    • Overview
  • Core-Reactnpm version

    • Overview
  • Stylesnpm version

    • Overview
  • Localizenpm version

    • Overview
  • Analyticsnpm version

    • Overview
  • Datepickernpm version

    • Overview
  • Icons-generatornpm version

    • Overview
  • Smart-addressnpm version

    • Overview
  • E2Enpm version

    • Overview
  • E2E-Playwrightnpm version

    • Overview

@uxf/analytics

npm size quality license

Client-side plumbing for Google Tag Manager, GDPR cookie consent, and A/B testing in UXF Next.js apps.

When to use

Use this package to manage the consent cookie, inject the GTM loader with a consent-aware default state, and assign/read A/B test variants. It is not an analytics dashboard or a data-collection SDK — event data still flows through GTM/GA that you configure yourself.

Each concern is a separate subpath import — there is no root @uxf/analytics entry:

  • @uxf/analytics/consent — read/write the cookie-consent cookie.
  • @uxf/analytics/gtm — GTM bootstrap script and consent updates.
  • @uxf/analytics/ab-testing — experiment assignment, provider, and hooks.

Installation

yarn add @uxf/analytics
npm install @uxf/analytics

Peer dependencies: @uxf/core, @uxf/core-react, and react >=18.0.2.

Cookie consent

@uxf/analytics/consent stores the four Google consent flags plus a version in a base64-encoded cookieConsent cookie (90-day TTL by default). Bump the version to invalidate old consent and re-prompt users.

Store consent to cookie

Client-only — throws if called on the server.

import { storeConsentToCookie } from "@uxf/analytics/consent";

storeConsentToCookie(
    {
        ad_personalization: true,
        ad_storage: false,
        ad_user_data: false,
        analytics_storage: false,
    },
    1, // version
);

Read consent from cookie

import { readConsentFromCookie } from "@uxf/analytics/consent";

const consent = readConsentFromCookie();
// { ad_personalization?, ad_storage?, ad_user_data?, analytics_storage?, version }

Check if consent is set

Returns true only when all four flags are booleans and the stored version matches.

import { isConsentCookieSet } from "@uxf/analytics/consent";

const isSet = isConsentCookieSet(null, 1); // (ctx, version) -> boolean

GTM

Initialize GTM

useGtmScript reads the consent cookie, builds the inline bootstrap script (sets the gtag default consent state, then loads GTM for the given container id), and returns it as a string. Render it in your document <head> — not directly in _app.

import { useGtmScript } from "@uxf/analytics/gtm";

const gtmScript = useGtmScript("GTM-YOURID");

// in your head component
<script dangerouslySetInnerHTML={{ __html: gtmScript }} />;

Update GTM consent

Stores the consent to cookie and pushes a gtag consent: "update" call plus a consent_resolved event to the dataLayer. Client-only.

import { updateGtmConsent } from "@uxf/analytics/gtm";

updateGtmConsent(
    {
        ad_personalization: true,
        ad_storage: false,
        ad_user_data: false,
        analytics_storage: false,
    },
    1, // version
);

A/B testing

A set of helpers, a provider, and hooks for running A/B tests in a Next.js app. Assignment happens in the proxy/middleware (a per-experiment uxf-experiment-* cookie), and variants are exposed to components through ABTestingProvider.

1. Define your experiments

Use as const satisfies ExperimentConfig[] so useABTestingVariant can infer literal variant names.

import type { ExperimentConfig } from "@uxf/analytics/ab-testing";

export const experiments = [
    {
        id: "1",
        traffic: 1,
        variants: [
            { name: "Control", traffic: 0.5 },
            { name: "B", traffic: 0.5 },
        ],
    },
    {
        id: "2",
        traffic: 0.5,
        variants: [
            { name: "Control", traffic: 0.5 },
            { name: "B", traffic: 0.5 },
        ],
    },
] as const satisfies ExperimentConfig[];

2. Assign variants in proxy.ts

handleABTesting sets a cookie per experiment (value not-participate for excluded users) and removes cookies for experiments no longer in the config.

import { handleABTesting } from "@uxf/analytics/ab-testing";
import { NextRequest, NextResponse } from "next/server";
import { experiments } from "./app/examples/ab-testing/constants";

export async function proxy(request: NextRequest) {
    const nextResponse = NextResponse.next();

    handleABTesting(request, nextResponse, experiments);

    return nextResponse;
}

3. Wrap the app with ABTestingProvider

The provider takes the assigned variants as [experimentId, variantName][] and fires an experience_impression GTM event on mount.

App Router — read the cookies via next/headers and map them with getExperimentsFromContext:

import { ABTestingProvider, getExperimentsFromContext } from "@uxf/analytics/ab-testing";
import { cookies } from "next/headers";

async function Layout(props: LayoutProps<"/examples/ab-testing">) {
    return (
        <ABTestingProvider
            experiments={getExperimentsFromContext(
                Object.fromEntries((await cookies()).getAll().map((v) => [v.name, v.value])),
            )}
        >
            {props.children}
        </ABTestingProvider>
    );
}

export default Layout;

Pages Router — inject the variants in getServerSideProps with addExperimentsSSR, then read them from pageProps:

import { addExperimentsSSR } from "@uxf/analytics/ab-testing";
import type { GetServerSideProps } from "next";

export const getServerSideProps: GetServerSideProps = async (ctx) => {
    return addExperimentsSSR(ctx, { props: {} });
};
import { ABTestingProvider, AB_TESTING_VARIANT_PROP_NAME } from "@uxf/analytics/ab-testing";

export default function App({ Component, pageProps }) {
    return (
        <ABTestingProvider experiments={pageProps[AB_TESTING_VARIANT_PROP_NAME]}>
            <Component {...pageProps} />
        </ABTestingProvider>
    );
}

4. Read the variant in a component

useABTestingVariant returns the variant name, or null when the experiment id is unknown or the user does not participate. Client component only.

"use client";

import { useABTestingVariant } from "@uxf/analytics/ab-testing";
import type { experiments } from "./constants";

function Page() {
    const variant = useABTestingVariant<typeof experiments>("1");

    return <div>Experiment 1 variant: {variant}</div>; // "Control" | "B" | null
}

export default Page;

API

@uxf/analytics/consent

Export Signature Description
storeConsentToCookie (consent: CookiesConsentType, version: number, cookieTtl?: number) => void Writes the consent cookie (default TTL 90 days). Throws on the server.
readConsentFromCookie (ctx?: AnyObject | null) => CookieConsentTypeWithVersion Reads and decodes the consent cookie. Pass a request-like ctx to read server-side.
isConsentCookieSet (ctx: AnyObject | null, version: number) => boolean true when all four flags are set and the stored version matches.
CookiesConsentType { ad_personalization?, ad_storage?, ad_user_data?, analytics_storage?: boolean } The four Google consent flags.
CookieConsentTypeWithVersion CookiesConsentType & { version: number } Shape stored in the cookie.

@uxf/analytics/gtm

Export Signature Description
useGtmScript (gtmId: string) => string Builds the inline GTM bootstrap script (default consent from the cookie + container loader).
updateGtmConsent (consent: CookiesConsentType, version: number) => void Stores consent and pushes gtag consent: "update" + consent_resolved. Client-only.
ConsentType "granted" | "denied" gtag consent value.
GtmConsentData type gtag consent payload / event union.
GtmDataLayer { push: (gtmEventData: unknown) => void } window.dataLayer shape (augments Window).

@uxf/analytics/ab-testing

Export Signature Description
handleABTesting (request, response, experiments: ExperimentConfig[], options?: { domain?: string }) => void Proxy/middleware: assigns and prunes experiment cookies.
getExperimentVariant (config: ExperimentConfig, randomNumberForTesting?: number | null) => ExperimentVariant | null Picks a variant by weighted traffic; null = not participating.
getExperimentsFromContext (cookies: Partial<{ [key: string]: string }>) => [string, string][] Extracts [id, variant] pairs from a server cookie map.
getExperimentsFromClient () => [string, string][] Extracts [id, variant] pairs from document.cookie.
addExperimentsSSR (ctx, pageProps) => pageProps Pages Router getServerSideProps helper; injects variants under AB_TESTING_VARIANT_PROP_NAME.
sendABTestingEvent (getExpVariantString?: GetExpVariantString) => void Pushes an experience_impression GTM event per experiment cookie. Client-only.
ABTestingProvider (props: { children; experiments: [string, string][]; getExpVariantString? }) => JSX Provides variants to the tree and fires sendABTestingEvent on mount.
useABTesting () => [string, string][] Returns all [id, variant] pairs from context.
useABTestingVariant <Config extends ExperimentConfig[]>(experimentId) => variantName | null Returns the variant name for one experiment, or null.
AB_TESTING_VARIANT_PROP_NAME "__AB_TESTING_VARIANT__" Page-prop key used by addExperimentsSSR.
EXPERIMENT_COOKIE_PREFIX "uxf-experiment-" Prefix of every experiment cookie.
ExperimentVariant { name: string; traffic: number; label?: string } A single variant.
ExperimentConfig { id: string; traffic: number; variants: ExperimentVariant[] } One experiment.
GetExpVariantString (cookie: { name: string; value: string }) => string Maps a cookie to the exp_variant_string sent to GTM.

Gotchas

  • No root import — always import from @uxf/analytics/consent, /gtm, or /ab-testing.
  • storeConsentToCookie throws when window is undefined (server). updateGtmConsent and sendABTestingEvent also run on the client only.
  • readConsentFromCookie works on both sides — pass a request-like ctx to read server-side; omit it on the client.
  • ABTestingProvider, useABTesting, and useABTestingVariant are client components ("use client").
  • Consent is stored base64-encoded in a single cookieConsent cookie; the version argument lets you re-request consent — isConsentCookieSet returns false on a version mismatch.
  • Experiment cookies are prefixed uxf-experiment-; excluded users get the value not-participate, so a variant is only meaningful when it matches a configured variants[].name.
  • The provider experiments prop is [experimentId, variantName][] — build it with getExperimentsFromContext (server) or getExperimentsFromClient (client), not the raw ExperimentConfig[].