---
title: "Cookieless product analytics you host yourself"
description: "Cookieless product analytics in your own database: funnels, replay, experiments and 13 month event retention. This prototype has no dashboard yet."
canonical: "https://laboetie.io/products/analytics"
language: en
---
# Analytics: cookieless product analytics

Analytics is a cookieless product analytics package that a site runs in its own stack. Before consent the package reads only the visitor choice from the device. No stored record holds an IP address. Session replay and experiments start after consent. This prototype has no dashboard yet, so reports on funnels, retention and paths are read through code.

Status: Prototype. Access: By request. Funnels, session replay and experiments with a cookieless default.

## Capabilities

- Consent states start from a cookieless default.
- Views, clicks, forms, rage clicks, web vitals and errors are captured automatically.
- Session replay masks inputs.
- Sticky experiments and feature flags are built in.
- Reports cover funnels, retention, paths, source cohorts and heatmaps.

## Three consent states

Consent sets one of three behaviors. In the default unknown state, the package reads and writes only the visitor choice on the device and keeps ids in memory. The granted state adds a persistent visitor, session storage, replay and surveys. In the denied state the library collects nothing and erases what it wrote to the device.

## Counting visitors without cookies

A cookieless visitor counts as one page load unless the site sets a secret. With a secret, the collector computes a keyed hash of the IP address and user agent under a key that rotates each UTC day, then discards the IP. Repeat visits by one visitor within one day count as one unit. This hash is pseudonymous data, so the site mentions it in its privacy policy.

## Funnels, retention, paths and heatmaps

The query helpers cover funnels, retention, paths, source cohorts and heatmaps. Funnels have ordered steps with any events allowed between them. Each report returns data to your code, so a developer reads it through a query or builds an admin page on a read only database user.

## Session replay with masked inputs

Session replay starts after consent. The collector rejects chunks collected without consent. The recorder masks every input, textarea, select and contenteditable element. No option turns that masking off. Chunks are stored per session for 30 days.

## Experiments and feature flags

Experiments give each visitor a sticky variant, and that assignment needs granted consent. Before consent a visitor sees the baseline and no exposure is recorded. A change of weights or a new variant keeps existing assignments as they are. A flag at full rollout is on for all visitors, while a partial rollout stays off until consent.


## What cookieless analytics means

**Cookieless analytics** measures how people use a website without storing an identifier on their device. Classic analytics tools write a cookie, which ties visits together and usually requires consent first. A large share of visitors refuses or ignores the banner, so the numbers go missing.

Cookieless tools count visits with no device storage, and that keeps a basic measurement available before any consent choice. The trade is real: you lose the ability to follow one person across several days.

Analytics takes a two state approach. Before consent it runs cookieless. Once a visitor accepts, it switches to full product analytics with a stable visitor, funnels across visits and session replay.

## How the two consent states work

| State | When | What runs |
|---|---|---|
| `cookieless` | Before any choice, or after a refusal | Page and interaction counts with no device storage |
| `granted` | After the visitor accepts | Persistent visitor, session storage, a durable event queue, click ids, replay, surveys and person properties |

You can choose a stricter mode. With `whenUnknown: 'off'`, nothing is collected before a choice. The package can also read the choice from your consent platform, so visitors see one banner.

## How cookieless visitors get counted

Without storage, the collector cannot tell whether two page loads came from one person. The collector offers two options.

| Setting | How a visitor is counted | Effect |
|---|---|---|
| No secret set | Each page load is one unit | The strictest option, and a hard navigation looks like a new visitor |
| Daily counting on | A short lived anonymous count | One visitor stays one unit for a day, and days cannot be linked |

No IP address is stored. Your privacy policy should still describe this measurement.

## What the client captures

Each module turns on or off by itself. Replay and surveys stay off until a project enables them.

| Module | What you get |
|---|---|
| `pageviews` | Page views with landing page and first touch source |
| `clicks` | Clicks with a label and a selector |
| `forms` | Starts, submits and abandons |
| `scrollDepth`, `engagedTime` | How far and how long people read |
| `rageClicks`, `deadClicks` | Frustration signals: repeated clicks and clicks that do nothing |
| `jsErrors` | Grouped JavaScript errors |
| `webVitals` | Real user performance measures |
| `trafficSource` | Where the session came from |
| `heatmaps` | Aggregated clicks and scroll depth |

Clicks store a label and a selector in place of the full element tree, which keeps stored data small.

## Web analytics, product analytics or session replay

Three families of tools answer different questions, and vendors often blur them.

| Family | Main question | Typical output |
|---|---|---|
| Web analytics | How many people came, and from where? | Visits, sources, top pages |
| Product analytics | What do people do, and where do they stop? | Funnels, retention, paths |
| Session replay | What happened in this one visit? | A video like recording |
| Experimentation | Which version works better? | Variants and lift |
| Server logs | What did the server receive? | Requests, including bots |

Analytics covers the first four in one package from one event stream. Separate tools each keep their own copy of your visitors, and their numbers rarely match. Logs remain useful for bots and errors, since AI crawlers run no JavaScript.

## Common measures

| Measure | Definition | Read it as |
|---|---|---|
| Unique visitors | Distinct units in a period | Depends on your consent state, so compare like with like |
| Sessions | Grouped activity of one visitor | The natural unit for traffic |
| Engaged time | Time with the page in focus and active | Better than time on page |
| Scroll depth | How far people read | Whether the page keeps attention |
| Conversion rate | Conversions divided by sessions | Compare per source |
| Rage clicks | Repeated clicks on one spot | Something looks clickable or fails |
| Dead clicks | Clicks that change nothing | A broken promise in the interface |

Cookieless numbers and consented numbers are different populations. Visitors who accept tend to differ from those who refuse, so avoid mixing them in one funnel without saying so.

## Core Web Vitals

Google publishes three measures of real user experience, and the `webVitals` module collects them from your visitors.

| Measure | What it covers | Good at the 75th percentile |
|---|---|---|
| LCP, Largest Contentful Paint | Loading of the main element | 2.5 seconds or less |
| INP, Interaction to Next Paint | Response to clicks and taps | 200 milliseconds or less |
| CLS, Cumulative Layout Shift | Visual stability | 0.1 or less |

Field data from real devices often looks worse than a test on your own laptop. Read it per page and per device.

## Write a tracking plan first

Inconsistent events break reports: `signup`, `Sign Up` and `user_registered` can all mean one thing. Agree on names before anyone writes code.

| Rule | Example |
|---|---|
| Use `object_action` in lower case | `quote_requested` |
| Use the past tense | `form_submitted`, never `submit_form` |
| Put variations in properties | `plan: 'pro'`, never `quote_requested_pro` |
| Keep personal data out of properties | No email, no name |
| Give each event one owner | The team that reads it |

A plan for a lead generation site can stay short.

| Event | Properties | Question it answers |
|---|---|---|
| `page_view` | Automatic | What do people read? |
| `calculator_started` | `product` | Do visitors engage? |
| `quote_requested` | `product`, `source` | Which pages produce leads? |
| `call_booked` | `product` | Do leads go further? |

Start with about ten events, and add one only when someone will read it.

## Tracking where visitors come from

The `trafficSource` module reads the referrer and the campaign tags of each session. **UTM tags** are parameters you add to a link, so a report can tell one campaign from another.

```text
https://example.com/offer?utm_source=newsletter&utm_medium=email&utm_campaign=spring
```

| Tag | Answers | Example |
|---|---|---|
| `utm_source` | Who sent the visitor? | `newsletter`, `linkedin` |
| `utm_medium` | Through what kind of channel? | `email`, `cpc`, `social` |
| `utm_campaign` | For which campaign? | `spring` |
| `utm_content` | Which link or version? | `header_button` |

Keep tags in lower case and write the list down, since `Email` and `email` become two sources in a report. Leave tags off internal links, because they overwrite the true source of the session.

Paid clicks carry their own click ids. Those are stored only with consent, so an offline conversion upload depends on the visitor having accepted.

## First party collection and ad blockers

A script served from an analytics vendor's domain sits on most blocklists. An endpoint on your own domain, such as `/api/site-events`, appears as part of your site, so fewer requests are lost. Privacy tools can still block it, and visitors keep their right to refuse. A first party endpoint improves data quality and leaves your consent duties unchanged.

Bots add noise from the other side. Many run no JavaScript and never reach the collector, while others do. Compare your analytics totals with your server logs from time to time.

## Install it in a Next.js site

The package installs as a private tarball, so the site keeps it in its own repository. Three pieces connect it.

The layout wraps the app in a provider.

```tsx
import { AnalyticsProvider } from '@infra/analytics/react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <AnalyticsProvider endpoint="/api/site-events" projectKey="my-site">
          {children}
        </AnalyticsProvider>
      </body>
    </html>
  );
}
```

A route on your own domain receives the events.

```ts
import { createCollector, createMongoSink, createRouteHandler } from '@infra/analytics/server';
import { getAnalyticsDb } from '../../../lib/analytics-db';

export const runtime = 'nodejs';

export const { POST } = createRouteHandler(() =>
  createCollector({
    sink: createMongoSink({ db: getAnalyticsDb, projectKey: 'my-site' }),
    projectKey: 'my-site',
    onError: (error) => console.error('[analytics]', error),
  }),
);
```

Any client component can then record a business event.

```tsx
const analytics = useAnalytics();
analytics.track('quote_requested', { plan: 'pro' });
```

The endpoint sits on your domain, so requests reach your own server. That makes the setup first party, and it means the events land in a database you already run.

## Session replay with masked inputs

**Session replay** records what a visitor did on the page so you can watch it back. Done carelessly, it records passwords and personal data.

Here, the recorder loads only with consent. Visitors without consent never download it. Every input, textarea and select is masked, along with any editable content, and no option turns that masking off. Setting `maskAllText: true` masks page text too, and `data-analytics-mask` masks one element.

Recordings are compressed, stored per session and kept for 30 days. The collector refuses a recording unless replay is on for the project and the event was collected with consent. The recorder weighs about 35 KB gzipped.

## Experiments and feature flags

An **A/B test** shows two versions to two groups and compares the results. Assignment needs a visitor who stays the same between page loads, so it needs granted consent.

Assignment sticks once made. Changing the weights or adding a variant moves nobody already assigned. The hook works in any component.

```tsx
const variant = useVariant('pricing-page');
const enabled = useFeatureFlag('new-checkout');
```

Flags live in code or come from your own server. There is no separate flag service to run.

## Funnels, retention and paths

Query helpers compute each report from your stored events.

```ts
const retention = await sink.queries.retention({ period: 'week' });
```

| Report | The question it answers |
|---|---|
| Funnel | Where do people drop out of a sequence of steps? |
| Retention | How many visitors come back after their first visit? |
| Paths | Which routes do people take through the site? |
| Source cohorts | Which traffic sources bring visitors who stay? |
| Heatmap | Where do people click and how far do they scroll? |

## A worked funnel

Take a quote form with four ordered steps. Over a month, the funnel returns these counts.

| Step | Visitors | Kept from previous step |
|---|---|---|
| Viewed the form | 10,000 | |
| Started the form | 3,200 | 32% |
| Reached the last step | 1,400 | 44% |
| Submitted | 980 | 70% |

Overall conversion is 9.8%. The largest loss sits at the first step, so start with the page above the form. Field level abandon events then show where people stop inside the form.

Read a funnel per source too. A step that works for search visitors can fail for paid social visitors, who arrive with less intent. The figures above illustrate the method and come from no client.

## A worked retention table

Retention groups visitors by the week of their first visit and counts how many return.

| First week | Week 0 | Week 1 | Week 2 | Week 3 |
|---|---|---|---|---|
| Cohort A | 100% | 24% | 15% | 12% |
| Cohort B | 100% | 28% | 19% | 16% |

Read down the columns, since a newer cohort that holds better means a change worked. A curve that flattens shows a group of people who keep coming back. These figures are also illustrative.

Retention needs a stable visitor, so it counts consented visitors. Cookieless days cannot be linked.

## Reading an experiment

A difference of a few conversions proves little, so let a test finish.

| Rule | Why |
|---|---|
| Choose the metric before you start | Otherwise you pick the winner after the fact |
| Fix the length in advance | Peeking inflates false wins |
| Run full weeks | Behaviour shifts between weekdays and weekends |
| Change one thing | You learn what caused the result |
| Keep a record of each test | Teams repeat old mistakes |

Small sites rarely reach enough conversions for a fair test. If a page gets a few conversions a week, use interviews and session replay in its place.

## Moving from a hosted analytics tool

1. Export or screenshot the reports you read, since history stays behind.
2. Write the tracking plan.
3. Run both tools side by side for a month.
4. Compare totals per source, and expect a gap between them.
5. Rebuild the reports your team reads each week through the query helpers.
6. Remove the old tag and update your privacy policy.

Two tools will never match exactly, because they differ on bots, consent and session rules. Look for stable trends.

## How to read a heatmap

A **heatmap** combines the clicks and scroll depth of many visitors on one page.

| What you see | What it often means | What to try |
|---|---|---|
| Clicks on something that is not a link | Visitors expect more detail | Make it a link |
| Few clicks on the main button | The offer or the wording is weak | Rewrite before you redesign |
| Most visitors stop halfway | The page lost them there | Move key proof higher |
| Heavy clicks on the menu | Visitors looked elsewhere for the answer | Add the answer to the page |

Read phones and desktops apart, since the layouts differ. A heatmap on a page with a few dozen visits shows noise, so wait for volume.

## What to do with frustration signals

Rage clicks and dead clicks turn vague complaints into a list of places to look.

1. Sort the pages by frustration events per visit.
2. Open a consented replay of a session on the worst page.
3. Note what the visitor expected.
4. Fix the cause, then compare the same measure a week later.

`jsErrors` groups JavaScript errors, so a bug that hits one browser appears as one line with a count. Set aside time each week to read that list, because users seldom report these errors themselves.

## Surveys in the page

A short question at the right moment can explain what numbers cannot. Surveys run with consent. Ask one thing, close to the event: "What almost stopped you from signing up?" on a confirmation page gives you the wording for your next headline. Keep answers free of personal data, and read them in full, since a count of themes loses the voice of the customer.

## Data retention and storage

Events default to a 13 month lifetime, and replay to 30 days. With consent, the visitor id lasts 13 months from creation, and a new visit does not extend it. You store the data in your own MongoDB.

| Data | Default lifetime |
|---|---|
| Events | 13 months |
| Consented visitor id | 13 months from creation |
| Session replay | 30 days |
| Raw IP | Never stored |

## Consent rules and your obligations

The cookieless mode was designed around the conditions the French data authority sets for audience measurement without consent: first party, for the publisher alone, statistics only, no cross site tracking and no merging with other processing. Nobody has audited or certified that design. Whether your deployment qualifies is a decision for your own counsel, and your site must still describe the measurement in its privacy policy and offer a way to refuse.

Replay, surveys and person properties fall outside that exemption and require consent.

## Consent banner design

Your banner affects how much consented data you collect. Regulators in several countries ask for refusing to be as easy as accepting.

| Do | Avoid |
|---|---|
| Offer accept and refuse at the same level | Hiding refuse behind a second screen |
| Name the purposes in plain words | Vague phrases such as "improve your experience" |
| Keep a way to change the choice later | A one time banner |
| Record the choice | Asking again on each page |

The package reads the state you give it, so any banner works. Keep the words on your banner in line with what the code does: if replay runs after consent, the banner must say so.

## Self hosted analytics or a hosted tool

| Question | Self hosted in your stack | Hosted tool |
|---|---|---|
| Where is the data? | Your database | The vendor's servers |
| Who pays for volume? | You, through your own storage | You, through the vendor's pricing |
| Who runs it? | Your team | The vendor |
| Dashboards | You build or wire them | Included |
| Blocked by ad blockers | Less often, since the endpoint is first party | More often, when the script loads from a vendor domain |

Choose a hosted tool if your team wants ready dashboards today. Choose a self hosted package if owning the data matters more and you already run a database.

## Questions to ask before choosing any analytics tool

1. Which questions must it answer in the next quarter?
2. Who reads the reports, and how often?
3. Where does the data live, and for how long?
4. What runs before consent?
5. Can you export raw events?
6. What happens to the cost when traffic grows tenfold?
7. Who fixes it when the numbers look wrong?

## Reporting rhythm

| Frequency | What to read | Who reads it |
|---|---|---|
| Weekly | Traffic, conversions, frustration signals | The team running the site |
| Monthly | Funnels and sources | Marketing and product |
| Quarterly | Retention and experiments | Leadership |

Keep the list short, and read it on a fixed schedule.

## Next step

Analytics is available on request. You receive a private package and its third party notices. [Send a brief](https://laboetie.io/brief) with your site, your consent setup and the funnels you need to read. It pairs with [Cortex](https://laboetie.io/products/cortex) for organic growth and with [Ads](https://laboetie.io/products/ads) for paid traffic.


## Frequently asked questions

### What is cookieless product analytics?

Cookieless product analytics is site measurement that stores no cookie or identifier on the visitor device. Before consent, this package keeps ids in memory and reads only the visitor choice from the device. Reports on funnels, retention and paths run on those events. A visitor cannot be followed across days in this mode.

### Do I need a cookie banner with cookieless analytics?

The package cannot settle that for you, because a site's own use of the data decides whether a consent exemption holds. The CNIL lists conditions such as use for the publisher alone, anonymous statistics and no cross site tracking. A site owner still has to describe the measurement in the privacy policy and offer a way to refuse. This describes code behavior and is no legal opinion.

### Does session replay work without consent?

No. Replay, surveys and person properties require consent in this package. The collector accepts replay chunks only when replay is enabled and the event was collected with consent. Every form field is masked in the recorder, and one option masks all page text as well. The site database keeps recordings per session for 30 days.

### How long does the package keep data?

Default event retention is 13 months against a CNIL recommended maximum of 25 months. Session replay chunks last 30 days per session. With consent, the visitor id lasts 13 months from creation, and a new visit does not extend it. These defaults apply in the MongoDB database of the site, which stays separate from its other data.

### What does autocapture record?

Autocapture records views, clicks, forms, rage clicks, web vitals and errors. It does not read field values. It stores the name and the type of a field and whether it is filled. Masking covers emails and long numbers in all strings. Keys such as email or password are dropped twice, once in the browser and once on the server.

### Does Analytics have a dashboard?

No. The roadmap calls the missing dashboard the largest gap of Analytics. Developers read funnel, retention, path and cohort data through the query helpers or through an admin page built on a read only database user. A team that needs ready made charts for non technical staff has to build them first.

### Where does the data live?

The data lives in a MongoDB database that the site owns. A collector route inside the site writes events through an ingest user limited to that database, and reports use a separate read user. The package installs as a vendored private tarball inside the site repository, so a deploy has no outside dependency. Third party notices travel with the package.
