Cookie Consent for Next.js, Gatsby, and Static Sites: A Developer's Integration Guide

The Static Site Consent Problem

Modern JavaScript frameworks like Next.js, Gatsby, and Nuxt.js introduced a paradigm shift in how web pages are built and delivered. Pages are pre-rendered at build time or on the server, then hydrated on the client. This creates a unique challenge for cookie consent: the consent banner must be ready before any tracking scripts execute, but the page itself may already be rendered and cached at the edge.

Traditional CMPs were designed for server-rendered PHP or simple HTML pages where the document loads linearly from top to bottom. In a framework world with code splitting, lazy loading, and streaming server-side rendering, the assumptions break. Getting consent right in these environments requires understanding the rendering pipeline.

Why Timing Matters More Than You Think

In a standard HTML page, placing a CMP script in the <head> before other scripts is straightforward. In Next.js App Router or Gatsby, the situation is more complex:

The core principle is this: consent must be established at the script level, not the component level. A React component that renders a consent banner is too late if it only becomes interactive after hydration.

Next.js App Router Integration

Next.js 13+ with the App Router introduced a new way to handle scripts. Here is the recommended approach for consent integration:

Step 1: Load the CMP Script in the Root Layout

Use the Next.js Script component with the beforeInteractive strategy in your root layout.tsx. This tells Next.js to inject the script into the initial HTML document, before hydration begins:

The beforeInteractive strategy is critical. The default afterInteractive strategy loads scripts after hydration, which is too late for consent. With beforeInteractive, the CMP script is included in the server-rendered HTML and executes as the page loads.

Step 2: Set Default Consent Before Google Tags

Before your Google Tag Manager or gtag.js snippet, include an inline script that sets default consent states. This ensures that even if GTM loads before the CMP banner appears, it respects the denied defaults:

This inline script should be placed in the <head> of your root layout, before the CMP and GTM scripts. In Next.js, you can use a regular <script> tag inside the <head> element of your layout for this purpose.

Step 3: Handle Route Changes

In single-page application navigation, the CMP script loads once but route changes do not trigger a full page reload. Your CMP must persist across client-side navigations. FlexyConsent handles this automatically — once loaded, it remains active across all route changes without re-initialization.

Next.js Pages Router Integration

For projects still using the Pages Router, the approach is similar but uses _document.tsx instead of the root layout. Place the CMP script in the <Head> component of your custom Document class. The beforeInteractive strategy works the same way in the Pages Router.

The key difference is that _document.tsx only renders on the server, so any consent logic here is guaranteed to be in the initial HTML payload.

Gatsby Static Site Integration

Gatsby generates fully static HTML at build time. There is no server-side rendering at request time, which simplifies some aspects but complicates others:

Gatsby's build-time approach means every HTML file on your CDN will include the consent script. This is actually ideal — there is no server logic to fail or cache incorrectly.

Nuxt.js Considerations

Nuxt.js (Vue-based) has its own patterns. In Nuxt 3, use the useHead composable or the nuxt.config.ts app head configuration to add the CMP script globally. Nuxt supports a body: false option (which places scripts in the head) and an async attribute for non-blocking loading.

For Nuxt's server-side rendering mode, the same principle applies: the CMP script must be in the initial HTML response, not dynamically injected by a Vue component after mount.

Avoiding Layout Shift

Consent banners are notorious for causing Cumulative Layout Shift (CLS), a Core Web Vital that affects SEO rankings. When a banner pops in after the page renders, it pushes content down or overlays it unexpectedly.

Strategies to minimize CLS from consent banners:

FlexyConsent's Framework-Agnostic Approach

FlexyConsent was designed to work with any framework — or no framework at all — by operating at the script level rather than the component level. Here is why this matters:

Developer tip: The simplest test for correct CMP integration is to open your browser's Network tab, filter by Google domains, and reload the page. No Google requests should fire before the consent default command appears in the console. If they do, your CMP is loading too late.

FlexyConsent's free plan supports unlimited pageviews and works with Next.js, Gatsby, Nuxt, Astro, SvelteKit, Remix, and plain HTML. The integration is the same across all of them: one script tag, properly placed.

← Blog Read All →