--- createdAt: 2026-07-08 updatedAt: 2026-07-08 title: Intlayer Analytics | Track content exposure and run A/B tests description: Discover how @intlayer/analytics tracks page/locale views and content exposure, and how to use it to run A/B tests on your Intlayer content. keywords: - Analytics - A/B Testing - Audience - Internationalization - Documentation - Intlayer - Next.js - JavaScript - React slugs: - doc - concept - analytics history: - version: 9.0.0 date: 2026-07-08 changes: "Init doc — @intlayer/analytics package, provider/node-level tracking, A/B testing, dashboard" author: aymericzip --- # Intlayer Analytics Documentation `@intlayer/analytics` is an optional companion package that tells you **which content is actually shown** to your visitors — which page, in which locale, and which specific piece of translated content — so you can understand your audience and run **A/B tests on content**. ## Table of Contents --- ## What it tracks `@intlayer/analytics` batches three kinds of anonymous events: | Event | Captured where | What it tells you | | ------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | `page_view` | Provider level (`IntlayerProvider`) | Which page and locale a session viewed, on first load, route change, or locale switch. | | `content_exposure` | Node level (`useIntlayer` / interpreter plugins) | Which dictionary key / key path was actually resolved and displayed — and, when part of an experiment, which **variant**. | | `conversion` | Wherever you call `useConversion()` | A goal reached (signup, click, purchase…) attributed to the A/B variant the session was exposed to. | Events are collected in memory and sent as a **single batched request roughly every 20 seconds** — never on every keystroke or render — so analytics never impacts first render time or adds a request per interaction. ## How it powers A/B testing on content Intlayer already lets you declare content [Variants](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dynamic_dictionaries/index.md) (e.g. a `hero-banner` dictionary with a `control` and a `black_friday` variant). `@intlayer/analytics` closes the loop: 1. `getVariant(experimentKey, variants)` deterministically assigns each anonymous session to a variant — a pure function of the session id and the experiment key, so the assignment is **stable across the session** and requires **no server round-trip** before first render (no flicker, no layout shift). 2. Every `content_exposure` event carries the `variant` that was shown. 3. `useConversion()` lets you attribute a goal (e.g. `"cta_click"`) to that variant. 4. The dashboard's experiment results endpoint compares conversion rates per variant, including statistical significance (a z-test). ## Installation `@intlayer/analytics` is a **peer, optional** dependency — never installed automatically by a framework package. Add it alongside `intlayer`: ```bash packageManager="npm" npm install @intlayer/analytics ``` ```bash packageManager="yarn" yarn add @intlayer/analytics ``` ```bash packageManager="pnpm" pnpm add @intlayer/analytics ``` ```bash packageManager="bun" bun add @intlayer/analytics ``` If you don't install it, every integration point resolves to a no-op — see [Zero-cost when not installed](#zero-cost-when-not-installed) below. ## Configuration Analytics **reuses the existing `editor` configuration block** — there is no separate `analytics` config schema to fill in: ```typescript fileName="intlayer.config.ts" codeFormat={["typescript", "esm", "commonjs"]} import type { IntlayerConfig } from "intlayer"; const config: IntlayerConfig = { editor: { backendURL: "https://back.intlayer.org", // Also used as the analytics ingestion endpoint clientId: "your-client-id", // Also used as the analytics project key clientSecret: "your-client-secret", }, }; export default config; ``` - `editor.backendURL` — the base URL analytics events are sent to (`POST {backendURL}/api/analytics/events`). - `editor.clientId` — the public project key attributed to every ingested event. It also acts as the **enable switch**: analytics stays fully disabled (and tree-shaken, see below) until `clientId` is configured. If you self-host Intlayer, analytics automatically points at your own instance since it shares `editor.backendURL`. ## Framework support Analytics is wired into the shared `IntlayerProvider` from `react-intlayer`, so it is available today anywhere that provider is used: | Framework | Status | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | React | ✅ Available | | Next.js (`next-intlayer`) | ✅ Available (via `react-intlayer`) | | React Native / Expo (`react-native-intlayer`) | ✅ Available (via `react-intlayer`) | | Vue, Svelte, Angular, Solid, Preact, Lit, Astro, Vanilla | 🚧 Planned — same client, provider-level bindings following the `@intlayer/editor` rollout pattern | ## Usage ### Automatic provider-level tracking No code changes are required. Once `@intlayer/analytics` is installed and `editor.clientId` is configured, `IntlayerProvider` automatically: - initializes the analytics client on mount, - records a `page_view` on initial load, - records a `page_view` on every locale change, - starts the ~20s flush loop and flushes any remaining events on unmount / tab close (via `navigator.sendBeacon`, falling back to `fetch(..., { keepalive: true })`). ### Automatic node-level tracking Every time `useIntlayer` resolves a piece of content for display, the interpreter reports a `content_exposure` event for that exact `dictionaryKey` + key path + locale — again, no code changes required. Repeated exposures of the same node within a flush window are coalesced into a single event with a `count`, so a list re-rendering 50 times doesn't send 50 events. ### Tracking conversions for A/B tests Use `useConversion()` to attribute a goal to the variant a session saw: ```tsx fileName="CTAButton.tsx" codeFormat="tsx" import { useConversion } from "react-intlayer"; const CTAButton = () => { const trackConversion = useConversion(); return ( ); }; ``` ### Resolving a variant client-side ```tsx fileName="useHeroVariant.ts" codeFormat="tsx" import { getGlobalAnalyticsClient } from "@intlayer/analytics/client"; const client = getGlobalAnalyticsClient(); const variant = client?.getVariant("homepage-hero", [ "control", "black_friday", ]); ``` ## Privacy & performance - **Anonymous by design**: sessions are identified by a rotating id; the backend only ever stores a **SHA-256 hash** of that id — never the raw id, never an IP address. - **Location is coarse**: only a country code, derived from CDN geolocation headers (`cf-ipcountry`, `x-vercel-ip-country`, …) — no IP is read or stored. - **URLs exclude search params** by default, so query strings are never captured. - **Sampling**: `sampleRate` lets you keep only a fraction of content-exposure events on high-traffic apps. - **Batched**: one request roughly every 20 seconds (`flushInterval`), or earlier if the buffer fills up (`maxBufferSize`) — never one request per event. ### Zero-cost when not installed `@intlayer/analytics` follows the exact same optional-dependency pattern as `@intlayer/editor`: - every integration point loads the package via a **dynamic `import()` wrapped in `try/catch`** — an app that never installs `@intlayer/analytics` never pays a bundle-size or runtime cost, and never sees an error; - a compile-time env var (`INTLAYER_ANALYTICS_ENABLED`), automatically set to `'false'` by `@intlayer/config` whenever `editor.clientId` is not configured, lets bundlers **dead-code-eliminate** the whole integration; - analytics is disabled inside the Intlayer editor/CMS preview iframe, so editor sessions are never counted as real traffic. ## Dashboard: Analytics page Once your project has collected events, the **Analytics** page in the [Intlayer dashboard](https://app.intlayer.org/analytics) (visible in the sidebar once a project is selected) shows: - **Active users** — distinct visitors over the selected rolling window (7 / 30 / 90 days). - **Users today** and **users over the last 7 days**. - **Page views** over the selected window. - An **evolution graph** of daily distinct visitors. - **Locales** and **Location** breakdown tabs, ranking your audience by locale and by country. ## Backend API reference All read endpoints require authentication; ingestion is public and attributed by `clientId`. | Method | Endpoint | Description | | ------ | ------------------------------------------- | -------------------------------------------------------------------------------- | | `POST` | `/api/analytics/events` | Ingest a batch of events (public, attributed by `clientId` in the body). | | `GET` | `/api/analytics/overview` | Page/locale totals for the authenticated project. | | `GET` | `/api/analytics/audience?days=30` | Distinct visitors, page views, daily series, locale + country breakdowns. | | `GET` | `/api/analytics/content-stats` | Per-content exposure totals, grouped by dictionary key / key path / locale. | | `GET` | `/api/analytics/experiments/:experimentKey` | Per-variant conversion rates and statistical significance for an A/B experiment. | You can also call these programmatically with the [CMS SDK](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md): ```ts fileName="analytics.ts" import { createIntlayerCMS } from "@intlayer/api"; import { analyticsEndpoint } from "@intlayer/api/analytics"; const cms = createIntlayerCMS(); const { data: audience } = await analyticsEndpoint(cms).getAudience(30); ``` ## Useful links - [Dynamic Dictionaries - Collections & Variants](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dynamic_dictionaries/index.md) - [Intlayer CMS - CMS SDK](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md) - [Intlayer Visual Editor](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_visual_editor.md) - [Configuration Reference](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/configuration.md) - [Self-Hosting Guide](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/self_hosting.md)