• CMSnpm version

    • Overview
    • ContentBuilder
    • InviteUserForm
    • LoginForm
    • RenewPasswordForm
    • WysiwygInput
  • 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
  • DnDnpm 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

ExportSignatureDescription
storeConsentToCookie(consent: CookiesConsentType, version: number, cookieTtl?: number) => voidWrites the consent cookie (default TTL 90 days). Throws on the server.
readConsentFromCookie(ctx?: AnyObject | null) => CookieConsentTypeWithVersionReads and decodes the consent cookie. Pass a request-like ctx to read server-side.
isConsentCookieSet(ctx: AnyObject | null, version: number) => booleantrue 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.
CookieConsentTypeWithVersionCookiesConsentType & { version: number }Shape stored in the cookie.

@uxf/analytics/gtm

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

@uxf/analytics/ab-testing

ExportSignatureDescription
handleABTesting(request, response, experiments: ExperimentConfig[], options?: { domain?: string }) => voidProxy/middleware: assigns and prunes experiment cookies.
getExperimentVariant(config: ExperimentConfig, randomNumberForTesting?: number | null) => ExperimentVariant | nullPicks 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) => pagePropsPages Router getServerSideProps helper; injects variants under AB_TESTING_VARIANT_PROP_NAME.
sendABTestingEvent(getExpVariantString?: GetExpVariantString) => voidPushes an experience_impression GTM event per experiment cookie. Client-only.
ABTestingProvider(props: { children; experiments: [string, string][]; getExpVariantString? }) => JSXProvides 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 | nullReturns 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 }) => stringMaps 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[].