Implementing CMP Consent Controls in Nuxt 3 & Vue.js Applications starts with separating three jobs. The CMP collects and manages the visitor’s consent state. Nuxt and Vue manage rendering, while Nuxt’s router or Vue Router manages application navigation.
GA4 records the resulting analytics data, while GTM or deliberately configured code can control when page-view events are sent. A change of route is not a new consent decision, and a stored consent choice should not be re-applied simply because Vue has rendered another page.
There is also a version consideration for existing projects. According to the Nuxt 3, the version reached end of life on 31 July 2026 and no longer receives bug fixes or security patches. The patterns below remain relevant to sites still running Nuxt 3, but teams maintaining those applications should also plan their migration path to a supported Nuxt release.
Why Nuxt 3 Changes the Consent Setup
In a standard Nuxt server-side rendering (SSR) setup, the first request can be rendered on the server. Vue then hydrates that HTML in the browser, attaching the client-side application to the existing markup. Later navigation can take place without another full document load.
That matters because server-rendered application code cannot assume that browser globals such as window, document or browser storage exist. It also means that loading a cookie banner on every Vue route is the wrong model: a site-wide CMP normally remains available while the router changes the rendered page.
Decide How the CMP and Analytics Will Be Installed
Before adding code, make two separate decisions.
- Choose one CookieScript installation method. The banner can be installed directly at site level or, for configurations where it is appropriate, through Google Tag Manager. Do not load the same banner both ways.
- Choose one owner for GA4 page-view measurement. That can be GA4 Enhanced Measurement using browser-history changes, deliberately manual Nuxt/Vue tracking, or GTM History Change tracking. These are alternative measurement architectures, not three layers to enable together.
There is an important exception to the first choice. CookieScript’s current geo-targeting guidance requires geo-targeted banner code to be installed directly in the website <head>, rather than through GTM. Global banner configurations without geo targeting can use the supported GTM installation where that architecture fits the site.
Add CookieScript to Nuxt 3
For a banner that should be present across the site, Nuxt’s application-level head configuration is a natural place to map the generated script into the site. Nuxt supports a static app.head configuration in nuxt.config, while useHead is available for reactive head values.
A simplified application-level pattern looks like this:
export default defineNuxtConfig({
app: {
head: {
script: [
{
src: 'PASTE_THE_GENERATED_COOKIESCRIPT_SRC_HERE'
}
]
}
}
})
This is an architectural example, not a replacement for the generated installation code. Copy the current code from the account and preserve any required attributes it contains. Script order depends on the configuration.
Scripts controlled through CookieScript’s blocking mechanism must load in the required order, while a direct gtag.js Consent Mode setup follows CookieScript’s documented order of default consent state, gtag.js, CookieScript banner code and then other scripts.
A client-only plugin is useful for browser-side integration code, event listeners or analytics callbacks, but it is not automatically a better place for a banner that must be placed early in the document head. Likewise, there is no reason to disable SSR globally just to make a CMP easier to load.
Keep Browser-Only Consent and Analytics Code Out of SSR
Nuxt provides explicit environment checks. import.meta.client is true in client code and import.meta.server is true on the server. Files in the plugins directory can also use the .client suffix when the complete plugin should run only in the browser.
At component level, Vue’s onMounted() hook is another place for browser-only side effects because it is not called during server-side rendering.
Use these boundaries for code that reads browser storage, accesses document.title, talks to window.gtag or integrates with a browser-only SDK.
Configure GA4 Around the Visitor’s Consent Choice
Google Consent Mode communicates consent state to supported Google tags. It does not detect Nuxt routes and it does not decide which navigation should count as a page view.
The consent types most relevant to this GA4 and advertising setup include analytics_storage, ad_storage, ad_user_data and ad_personalization. In Basic Consent Mode, Google tags are prevented from loading or sending data until the required consent is granted.
In Advanced Consent Mode, Google tags can load with denied defaults and send cookieless measurements before the visitor grants storage consent. The CMP or application remains responsible for storing the user’s choice and updating Google’s consent state when that choice changes.
Consent Mode should therefore be configured independently of the page-view mechanism. Google Consent Mode does not automatically make arbitrary non-Google technologies respond to the visitor’s consent choice. Non-Google tags need suitable script blocking, GTM consent checks or another deliberately configured consent mechanism.
Avoid giving several systems independent control over the same tag. If CookieScript automatic blocking, GTM consent checks and custom Vue loading logic all try to decide whether the same analytics script can run, troubleshooting becomes unnecessarily difficult.
Basic Consent Mode creates another timing issue in a manual analytics architecture. In a manual Basic Consent Mode setup, code that attempted to send a page view before the Google tag became available will not automatically run again when analytics later becomes available.
The setup therefore needs to coordinate the first permitted page view with consent and tag availability and deduplicate it against any measurement already sent.
Make Sure GA4 Measures Nuxt Client-Side Navigation
Do not start by adding a router callback. For implementations using the Google tag directly, Google’s current single-page application measurement guidance recommends browser-history Enhanced Measurement where the application uses the History API. Test that approach first.
If GA4 is configured through Google Tag Manager, however, Google says not to enable automatic GA4 history tracking; use the GTM SPA measurement pattern instead to avoid duplicate page views.
With the appropriate Enhanced Measurement option enabled, GA4 can generate page views from browser history changes. For many sites using the Google tag directly, that may cover client-side navigation without custom analytics code.
Test the initial document load, several internal links, browser Back and Forward, direct entry to an internal URL and relevant query-string changes. For every navigation that the measurement plan treats as a page, GA4 should receive one intended page_view.
Query changes need a product decision as well as a technical one. If a query parameter represents a genuinely new screen, recording another page view may be appropriate. If it merely changes a filter on the same screen, automatic history measurement may be broader than the desired analytics definition.
When Manual Tracking Makes Sense
Manual tracking is useful when automatic history measurement does not match the application’s route semantics or when the development team needs explicit control.
Vue Router documents router.afterEach() as a suitable global after-navigation hook for analytics. Nuxt also exposes client-side hooks including page:finish, which runs after the Nuxt page’s Suspense resolution. They represent different timing choices. Do not register both to send the same page view.
Before using a manual approach, disable the competing automatic measurement described below.
The following deliberately manual example uses a client-only plugin. It sends the initial page after the Nuxt app has mounted, then registers afterEach() for later successful navigation. Because Vue Router’s global after-hooks run before DOM updates are applied, the example waits for Vue’s next DOM update before reading document.title:
// plugins/ga4-pageviews.client.js
import { nextTick } from 'vue'
export default defineNuxtPlugin((nuxtApp) => {
const router = useRouter()
const sendPageView = () => {
if (typeof window.gtag !== 'function') return
window.gtag('event', 'page_view', {
page_title: document.title,
page_location: window.location.href
})
}
nuxtApp.hook('app:mounted', () => {
sendPageView()
router.afterEach(async (_to, _from, failure) => {
if (failure) return
await nextTick()
sendPageView()
})
})
})
The window.gtag check only prevents the example from calling a function that is not available. It is not a test of the visitor’s consent state. Consent must already be handled by the selected Basic or Advanced Consent Mode architecture.
The example also assumes that the Google tag is available when an intended page view is sent. Under Basic Consent Mode, an initial call that returns because gtag is unavailable will not be replayed automatically when consent is later granted.In production, this manual pattern therefore needs to coordinate the first permitted measurement with consent and tag availability.
If a page title depends on asynchronous data or other delayed useHead() updates, test the title timing separately. Nuxt’s page:finish hook is another possible timing point when measurement needs to wait for the page’s Suspense resolution, but it should not be used alongside afterEach() to report the same page view.
If this approach is used, it becomes the measurement owner. Google’s manual page-view documentation explains that send_page_view: false suppresses the automatic page view associated with a Google tag configuration, but that setting alone does not disable Enhanced Measurement page views triggered by browser-history events. Disable the competing history-based page-change option in the GA4 web stream as well.
The minimal example also does not promise ideal virtual-route page_referrer attribution for every application. Google’s SPA guidance specifically recommends checking page_location and page_referrer. If referral behaviour matters to reporting, test it separately rather than assuming a basic custom event reproduces the desired virtual-navigation chain.
Using Google Tag Manager Instead
GTM can control virtual page-view tracking instead of framework code. Google provides a current GTM SPA measurement pattern using History Change triggers.
In that architecture, keep the initial Google tag configuration and use a GTM History Change trigger for subsequent virtual navigation. GTM’s built-in History variables can also be enabled when filtering history changes or implementing additional navigation logic. Google’s pattern updates values such as page_location and page_title before sending the GA4 page_view.
If GTM owns that tracking, do not also call gtag('event', 'page_view') from afterEach() or page:finish. Disable competing GA4 automatic history measurement where the GTM pattern requires manual history tracking.
The banner can also be installed through GTM for supported non-geo-targeted configurations. Its current GTM documentation recommends the CookieScript template and a Consent Initialization trigger when using the integration with Google Consent Mode.
Handle Consent Changes Separately From Route Changes
CookieScript exposes custom consent events for cases where custom code needs to react to consent behaviour. These include CookieScriptAcceptAll, CookieScriptAccept, CookieScriptReject and CookieScriptLoaded, together with category-specific events.
CookieScriptLoaded is a readiness event: it indicates that the CookieScript instance is available. It is not evidence that the visitor has just made a new consent choice. The accept and reject events represent user interactions with consent.
Category events have another important behaviour: they fire once per page and can fire on page load when the visitor previously accepted that category. In a client-side route transition there is no new document load, so do not treat those events as a substitute for Vue Router or Nuxt navigation hooks.
A route change can cause analytics to record another page while the same stored consent decision remains in force. A later consent-preference change can alter which technologies may run while the visitor stays on the same route. Those are independent transitions.
Test Consent and Analytics Across Nuxt Navigation
Test the production architecture rather than only checking that a banner appears in development.
- Start clean. Remove stored consent and test a first visit, the default consent state and network activity before any choice is made.
- Reject non-essential categories. Confirm that technologies which require consent behave according to the selected Basic or Advanced Consent Mode architecture and the site’s blocking configuration.
- Grant analytics consent and accept all. Test these states separately where the banner exposes different choices, and verify the resulting consent updates.
- Change and withdraw consent. Reopen preferences and make sure later changes are reflected without requiring a route transition.
- Reload with stored consent. Confirm that the saved decision is restored correctly and that initialization events are not mistaken for a fresh user choice.
- Exercise navigation. Test the initial page, several internal routes and any query-string transitions that should or should not count as page views.
- Use browser history. Test Back and Forward and confirm that each intended virtual page produces exactly one GA4 page view.
- Test direct internal entry and hydration. Open a nested URL directly, confirm the SSR-to-hydration path and watch for browser-global or hydration errors.
- Run a production build. Repeat the tests on mobile and major supported browsers rather than relying only on the Nuxt development server.
- Inspect the evidence. Use browser DevTools to inspect cookies, local storage, session storage where applicable, loaded scripts, network requests, GA4
collectrequests and console errors. Verify analytics with Google Tag Assistant and GA4 DebugView.
With Advanced Consent Mode, the existence of a Google network request does not by itself prove that analytics_storage was granted. Inspect the consent state as well as the request.
Common Nuxt 3 / Vue Cookie-Consent and GA4 Mistakes
- Using browser globals during SSR: accessing
window,documentor storage from universal code without a client boundary. - Loading the CMP twice: installing the same banner directly and through GTM.
- Reinjecting the banner after navigation: treating every Vue route as a new consent session.
- Combining blocking authorities: letting CookieScript blocking, GTM rules and custom loaders independently control the same script without a deliberate reason.
- Enabling automatic and manual page views: leaving GA4 browser-history measurement active while a router or GTM trigger sends the same route.
- Duplicating the initial page: sending a manual first page view while the Google tag configuration sends another automatically.
- Missing virtual routes: measuring only the initial document when client-side navigation is not covered by the selected analytics architecture.
- Registering duplicate listeners: sending the same event from
afterEach(),page:finishand another analytics plugin. - Mixing analytics stacks: combining direct
gtag.js, GTM and custom Nuxt tracking without assigning clear ownership. - Using consent events as route events: assuming an accept, reject, category or initialization event represents Vue navigation.
- Testing development only: missing production-only script order, hydration, caching or deployment differences.
- Assuming every Nuxt site renders identically: ignoring that Nuxt supports different rendering and deployment strategies.
Managing Consent With CookieScript
For these applications, the platform can handle consent collection, storage and script controls without becoming part of the routing logic. Nuxt/Vue continues to manage rendering and client-side navigation, while GA4, GTM or another selected analytics setup remains responsible for page-view measurement.
CookieScript is a Consent Management Platform (CMP) that Google includes among the CMP partners available for Consent Mode setup. It is also a Google-certified CMP with Gold tier status.
Features most directly relevant to this setup include:
- Cookie Banner: provides the interface for collecting visitor consent choices.
- Google Consent Mode v2: communicates visitor consent choices to supported Google services through consent types such as
analytics_storage,ad_storage,ad_user_dataandad_personalization. - Google Tag Manager integration: supports consent-aware Google tag configurations when GTM is part of the site’s measurement architecture.
- User consent recording: records visitor consent where consent recording is enabled, independently of Nuxt route changes.
- Consent events: allow application code to respond to consent actions and CMP readiness without using those events as substitutes for Vue Router or Nuxt navigation events.
- Automatic script blocking: can prevent configured non-essential scripts from running until the required consent is available. Where GTM consent controls or custom Nuxt/Vue loading conditions are also used, responsibilities should be clearly defined to avoid overlapping controls.
- Geo targeting: allows consent configurations to vary according to visitor location. Geo-targeted banner code must be installed directly in the website
<head>rather than through GTM. - Cookie Scanner: identifies cookies that may need to be included in the site’s consent and blocking configuration.
CookieScript also offers:
- Automatic monthly scans: rescan the website regularly as its cookie usage changes.
- Advanced reporting: provides information about consent and banner activity.
- 42 languages: supports multilingual cookie banners for websites serving visitors in different languages.
- Cookie Banner sharing: allows banner configurations to be shared between accounts.
- IAB Transparency and Consent Framework (TCF) 2.3 integration: supports implementations that use the IAB TCF.
- Privacy Policy Generator: provides a tool for generating privacy-policy content.
- Cookie Policy Generator: provides a tool for preparing cookie-policy information.
- Global privacy regulation support: The platform provides configuration options intended to help websites meet cookie-consent and privacy requirements associated with the General Data Protection Regulation (GDPR), ePrivacy Directive, California Consumer Privacy Act (CCPA), California Privacy Rights Act (CPRA), Lei Geral de Proteção de Dados (LGPD) and Personal Information Protection and Electronic Documents Act (PIPEDA).
CookieScript gives you a 14-day free trial of the Plus plan with no credit card required.
Conclusion
A dependable setup is easier to maintain when consent, navigation and analytics each have a clearly defined responsibility. That separation also makes failures easier to diagnose: a developer can tell whether a problem comes from consent state, route handling, script availability or measurement rather than having several systems react to the same event.
Frequently Asked Questions
Where should a CMP be added in Nuxt 3?
For an application-wide direct installation, Nuxt’s application head configuration is suitable for mapping a generated CMP script into the document head. CookieScript’s Cookie Banner Set-Up Guide covers the general banner installation process. CookieScript can also be installed through GTM for configurations where that method is supported; see Google Tag Manager and Cookie Consent for broader guidance. A geo-targeted CookieScript banner should be installed directly in the head.
Does the consent banner reload after every Nuxt route change?
Normally, no. Client-side navigation changes the rendered route without creating a new consent decision. The cookie banner should remain at site level, while the stored preference continues to apply until the visitor changes it. CookieScript’s consent events should not be used as router events.
Why does GA4 record only the first page in a Nuxt application?
The initial document may be measured while later client-side navigation is not. CookieScript’s guide to Google Analytics 4 cookies and consent provides additional context on GA4 measurement and consent. If GA4 uses the Google tag directly, check whether Enhanced Measurement is detecting browser-history changes. If GA4 is configured through GTM, check the GTM History Change implementation instead and keep competing GA4 automatic history tracking disabled. Add manual Nuxt/Vue tracking only when the selected measurement approach does not fit the application.
Why does GA4 record two page views for one route?
A common cause is overlapping page-view owners: for example, Enhanced Measurement plus a Vue Router callback, or a GTM History Change trigger plus a custom gtag() call. This is especially important when combining Google Tag Manager and cookie consent with GA4 tracking. Google’s page-view documentation also notes that send_page_view: false does not by itself disable history-based Enhanced Measurement events.
Should I use Vue Router afterEach or a Nuxt page hook for GA4?
Use the hook whose timing and route semantics match the application, then test it. Vue Router explicitly lists afterEach() as useful for analytics, while Nuxt’s page:finish runs after the Nuxt page Suspense has resolved. Whichever measurement method is selected, CookieScript’s guide to GA4 cookies and consent provides additional context on the consent side. Do not register both hooks to send the same page view.
Can CookieScript and GTM be used together with Nuxt 3?
Yes, where the banner configuration supports GTM installation. The CookieScript article on Google Tag Manager and cookie consent covers how GTM, consent management and tracking tags interact. CookieScript also documents a GTM template installation and a separate Consent Mode v2 setup. Do not install the same banner both directly and through GTM, and install geo-targeted banner code directly in the head.
Does Google Consent Mode track Nuxt route changes?
No. Google Consent Mode v2 communicates the visitor’s consent choices to supported Google services; it does not act as a Nuxt route tracker. Google’s Consent Mode documentation describes how consent state affects supported tags. Route and page-view measurement remain separate responsibilities handled by GA4 automatic history measurement, GTM or application code. For the differences in tag behaviour before consent, see Basic vs Advanced Google Consent Mode v2.
How should CookieScript and GA4 be tested after client-side navigation?
Test consent states and route states independently. CookieScript’s Cookie Banner Set-Up Guide provides useful context for checking banner and consent configuration, while its guide to GA4 cookies and consent covers the analytics side. Navigate through internal links, Back, Forward and relevant query changes, then inspect browser storage, scripts and GA4 network requests. Use Tag Assistant and DebugView to confirm that each intended route produces one page view and that consent changes are reflected separately.