Skip to main content
Next.jsAdSensePrivacyConsent ModeGDPR

Consent Mode v2, Cookie Banners, and AdSense on a Next.js Portfolio

elango
elango

Full Stack Engineer

7/13/2026
5 min read
Consent Mode v2, Cookie Banners, and AdSense on a Next.js Portfolio

Why consent architecture matters on elangodev.com

When I prepared elangodev.com for Google AdSense, the hard part was not pasting a publisher snippet. It was aligning privacy law defaults with crawler verification and a usable cookie banner. EEA visitors need Consent Mode v2 signals. AdSense still needs to see the tag for site review. Those requirements conflict unless you design the load path carefully.

This article documents the production setup I ship: lib/consent.js, CookieConsent.jsx, AdSenseScript.jsx, and privacy copy that matches the actual cookies. It is a case study from one portfolio, not a generic compliance checklist.

Default denied, then upgrade after choice

Consent Mode v2 expects ad_storage, ad_user_data, and ad_personalization to start denied in regions that require it. On this site I set defaults in a small consent helper before analytics or ads request personalized storage.

// lib/consent.js — conceptual shape
export const CONSENT_KEY = "elangodev_cookie_consent";

export function applyConsentDefaults() {
  if (typeof window === "undefined" || !window.gtag) return;
  window.gtag("consent", "default", {
    ad_storage: "denied",
    ad_user_data: "denied",
    ad_personalization: "denied",
    analytics_storage: "denied",
  });
}

export function grantAllConsent() {
  window.gtag("consent", "update", {
    ad_storage: "granted",
    ad_user_data: "granted",
    ad_personalization: "granted",
    analytics_storage: "granted",
  });
}

Essential-only mode keeps the site usable: theme preference and the consent decision itself stay local. Analytics and personalized ads stay off until the visitor chooses Accept All.

Banner UX that is more than Accept

Early drafts of my banner only offered Accept. That failed my own AdSense readiness WARN for reject/essential paths. The production banner offers Accept All and Essential Only, persists the choice under CONSENT_KEY, and exposes Cookie Settings in the footer so people can change their mind.

I link the banner to /privacy and /cookie-policy with the same publisher ID and third-party list. Reviewers notice when the policy page describes cookies the banner never offers to control.

Loading AdSense without consent gating the library forever

If the AdSense script only mounts after Accept, Google's crawler may never verify the tag. My compromise: load the library site-wide via AdSenseScript for verification, but gate personalized adsbygoogle pushes behind consent in AdUnit.jsx.

// Conceptual AdUnit gate
if (consent !== "accepted") {
  return null; // no personalized ad request
}
// else push adsbygoogle slot

That pattern keeps review crawlability while respecting Consent Mode for real users. The privacy policy states this explicitly so the UX and legal text stay aligned.

What broke during AdSense readiness checks

My internal npm run check:adsense suite caught three issues before reviewers did:

  • Privacy copy missing the word AdSense (easy FAIL).
  • Consent Mode keys incomplete — analytics only, no ad_user_data.
  • Footer missing editorial and privacy links on some layouts.

Fixing those was faster than guessing after a rejection. The suite is heuristic, but it forced the site to tell a consistent story about ads and cookies.

Operational habits after shipping

Whenever I change monetization, I update three places together: consent helper, cookie policy, and privacy advertising section. I also re-test Essential Only in an incognito window to confirm GA and personalized slots stay quiet.

Consent Mode on a personal portfolio is still real engineering. Treat it like any other product surface: defaults, upgrade path, and documentation that matches the code.

Testing matrix I run before each AdSense resubmit

Before I request another AdSense review, I walk a short matrix on production:

  • Incognito, Essential Only — confirm no personalized ad requests fire from AdUnit.
  • Incognito, Accept All — confirm Consent Mode updates and ads may request inventory.
  • View-source / rich results — confirm the AdSense script tag is present for crawlers.
  • Privacy and cookie policy — confirm publisher ID and Consent Mode wording still match code.

I also keep Mediapartners-Google and AdsBot-Google allowed in app/robots.ts so review bots are not blocked by the tighter Googlebot allowlist I use for human-facing indexing.

When something fails the matrix, I fix the code first, then the policy pages, then re-run npm run check:adsense. Re-requesting review without that loop is how sites burn cooldown time on the same low-value or policy signals.

Consent Mode is not a one-time paste. On a living Next.js portfolio it is a product surface that must stay aligned with monetization, analytics, and editorial quality goals at the same time.

Finally, I treat cookie banners like any other UX: keyboard accessible, readable contrast, and never blocking the entire page behind a dark overlay that looks like a soft paywall. Reviewers notice hostile consent UI as quickly as they notice thin articles.

When I change AdSense slot IDs or publisher configuration, I verify the privacy page publisher ID string still matches NEXT_PUBLIC_ADSENSE_PUBLISHER_ID. Drift between env config and legal copy is a small detail that undermines trust during manual review.

The long-term goal is boring reliability: consent defaults stay denied, upgrades are explicit, crawlers can verify the tag, and readers can revoke personalization from the footer without hunting through browser settings alone.

elango

Written by

elango

Full Stack Engineer & Software Architect specializing in React, Next.js, and high-performance web applications.

Share on:

Discover More

View blog