Guide
Consent is an analytics execution boundary
A practical Next.js pattern that keeps optional analytics code and collection behind an explicit visitor decision.
- Next.js
- Analytics
- Privacy
- Frontend architecture
Many sites build analytics consent as a banner over an unchanged page. The banner asks for permission, but the page has already downloaded a tracker, created identifiers, or started collection. A real consent boundary controls what runs, not only what shows.
Start with a denied default
Until the visitor accepts, the page must act as if the visitor rejected optional analytics. That makes the undecided state safe by design. Public content, navigation, and core features stay available in that state.
"Optional analytics" needs a precise line. On this site, the line is identification. Vercel Analytics counts page views. Speed Insights reports page speed. Neither sets a cookie, and Vercel discards the request hash that tells visits apart after 24 hours, so the counts stay aggregate. Those two run for every visitor. Microsoft Clarity and Google Analytics can connect one visitor's activity across pages and visits, so they sit behind the boundary.
The site moved to this line on 2026-10-17. Before that, all four waited for consent. Most visitors never answered the panel, so the site could not see even how many people read it.
One client component owns the decision. It reads a versioned consent record from browser storage after mount. Before that read finishes, the page renders no analytics runtime. A rejected record keeps the runtime out. An accepted record lets the app request it.
Defer the code as well as the component
A conditional render does not always defer a package. A static import can put an optional analytics SDK in the initial client graph, even when React returns null. The stronger boundary uses a dynamic import, requested only after acceptance.
That distinction matters for privacy and for speed. An undecided visitor should not pay to download, parse, or run optional tooling. A rejected visitor never needs that code.
Hover a part to see its nameCentralize provider behavior
Several analytics providers drift apart when each reads consent on its own. One may start early. Another may fail to stop after withdrawal. The privacy copy may promise behavior that the code does not deliver.
The site therefore has one analytics boundary and one consent record. That boundary activates Microsoft Clarity and Google Analytics. Provider-specific code stays inside the runtime layer. The two anonymous counters sit outside it, in plain view in the same component, so a reader of the code sees both sides of the line in one place.
Withdrawal matters as much. Both gated services accept a denied-consent signal, so both receive it, and the site deletes their cookies. The page then reloads in the rejected state, so optional collection cannot resume.
When the line moves, the consent record's version moves with it. Every visitor who chose under the old terms is asked again.
Hover a part to see its nameVersion the decision
A stored boolean cannot explain which terms a visitor accepted. The record should include at least the decision, a consent version, and a timestamp. When the terms change, the app can treat an older version as undecided and ask for a new choice.
The timestamp is not a server audit log. It is a local record that helps the site apply the visitor's choice every time. The privacy notice should say where the record lives and what it does.
Hover a part to see its nameKeep deployment context explicit
Local development and preview deployments do not need production analytics. Turning collection off there keeps test traffic and preview URLs out of the reports and avoids accidental tracking.
This site enables the analytics configuration only on the Vercel production deployment. Preview pages also send noindex controls. Consent still applies in production.
Test the boundary, not only the banner
Test what a browser can observe:
- No request from a provider that can connect visits occurs before a decision.
- The anonymous counters set no cookie.
- Rejecting consent leaves the site fully usable.
- Accepting consent loads each approved provider.
- Withdrawing consent removes optional runtimes and preserves the rejected record.
- An older consent version causes a new prompt.
- Public privacy copy matches actual provider behavior.
The banner is the visible part. The execution boundary is the privacy feature.