image

Integrating Google Analytics While Ensuring User Privacy with a Consent Cookie Banner in NextJs 14

by:
user avatar

SpiralSrc

Dec 1, 2024
38
0

Having a website means you have an opportunity to understand how users interact with your content, which is key to improving the user experience and offering relevant content that resonates with your audience. Tracking page views, user interactions, and traffic sources allows you to make data-driven decisions that enhance your site’s performance and tailor your content to meet users' needs. Google Analytics provides powerful tools for gaining these insights, but with the ability to track comes the responsibility to respect user privacy.

In this blog, I will walk you through how I integrated Google Analytics into my Next.js 14 project while prioritizing user transparency. Setting up a consent cookie banner is a critical step, not just to comply with privacy laws, but to build trust with your users. By informing users about tracking and allowing them to opt in or out, you can create a transparent and ethical data collection process.

1. Create and configure a Google Analytics account for your website

To integrate Google Analytics into our project, the first step is to set up a data stream. This involves adding the script provided by Google to our project and copying the Measurement ID, which is required to link our website to the Analytics account. The Measurement ID serves as a unique identifier, enabling Google Analytics to track and collect data about user interactions on our site.

Below are the steps to create a Google Analytics account, set up a property, configure a data stream, and integrate the Measurement ID into your project:

1.1 Create a Google Analytics Account

  • Go to Google Analytics website.
  • Sign in with your Google account and click "Start Measuring."
  • Provide a name for your account and configure data-sharing settings.

1.2 Set Up a Property

  • Create a new property by entering a name (e.g., "My Website").
  • Fill up multi-step form (reporting time zone and currency, business details, business objectives on which data you want to collect, and accepting/signing the Google Analytics terms and conditions).
  • Choose your app's platform (web , android or ios) and in this example, I chosed the "web" platform.

1.3 Configure a Data Stream

  • Enter your website's URL and stream name.
  • Click "Create and Continue".
  • A window will pop up with the script you will need to add into your app.

1.4 Get Your Measurement ID

  • In the property settings, navigate to Admin > Data Streams and select your stream.
  • Copy the "Measurement ID" (e.g., G-XXXXXXXXXX) visible in the stream details.

1.5 Add the Measurement ID to Your Project

  • Copy and paste your measurement ID into your Google Analytics script or environment variable. In NextJs, environment variables that need to be accessed on the client side must be prefixed with NEXT_PUBLIC_. This ensures that only the variables meant for client-side use, like the Google Analytics Measurement ID, are exposed to the browser, while keeping sensitive information secure on the server. By adding the prefix, Next.js makes these variables accessible throughout your application, allowing you to integrate Google Analytics and other client-side services without compromising security.
1# Google Analytics 2NEXT_PUBLIC_GOOGLE_ANALYTICS_ID="G-XXXXXXXXXX"

2. Implementing Cookie Consent Banner

Before integrating the Google Analytics script into our project, it's important to prioritize creating a cookie consent banner. User privacy is a fundamental value for me, and I believe in ensuring transparency by informing users about the use of cookies on my website. This includes obtaining their permission to track page views, traffic, and user interactions through Google Analytics. By implementing a consent banner first, we can demonstrate respect for users' preferences and comply with privacy regulations, fostering trust and accountability.

2.1 Create a Local Storage Helper

To simplify interactions with localStorage, we can create a reusable utility file "storage-helper.ts" in your "/utils" or "/lib" folder. This file contains functions for getting and setting data in localStorage, handling JSON parsing and stringifying, and providing error handling. While this utility can be used to manage cookie consent, it is designed to work for any feature in your project that requires localStorage.

/utils/storage-helper.ts

1// Utility function to get a value from localStorage 2export function getLocalStorage<T>(key: string, defaultValue: T): T { 3 try { 4 const stickyValue = localStorage.getItem(key); 5 return stickyValue !== null ? JSON.parse(stickyValue) : defaultValue; 6 } catch (error) { 7 console.error(`Error parsing JSON for key "${key}":`, error); 8 return defaultValue; 9 } 10} 11 12// Utility function to set a value in localStorage 13export function setLocalStorage<T>(key: string, value: T): void { 14 try { 15 localStorage.setItem(key, JSON.stringify(value)); 16 } catch (error) { 17 console.error(`Error setting localStorage key "${key}":`, error); 18 } 19}

We are going to be using the above getting and setting data utility function to and from the localStorage to save or get consent value set by our users.

2.2 Build a Cookie Banner Component with Consent Handling

In this step, we create a CookieBanner.tsx component that manages user consent for cookies and updates Google Analytics based on the consent status. This component utilizes the reusable storage-helper.ts functions for handling localStorage operations and ensures that the banner only appears when necessary.

CookieBanner.tsx

1"use client"; 2 3import { getLocalStorage, setLocalStorage } from "@/lib/storage-helper"; 4import { useEffect, useState } from "react"; 5 6export default function CookieBanner() { 7 const [cookieConsent, setCookieConsent] = useState<boolean | null>(null); 8 const [showCookieBanner, setShowCookieBanner] = useState(false); 9 10 useEffect(() => { 11 // Getting the cookie_consent value from the localStorage using the storage-helper utility function we created at step 2.1 12 const storedConsent = getLocalStorage<boolean | null>( 13 "cookie_consent", 14 null 15 ); 16 17 // I used this showCookieBanner with boolean value to be able to reuse this same consent component if user go to user's setting to change consent if they change their mind. In the cookie settings, you can add a button to set the "showCookieBanner" as true in order for this consent component to show up again. This is initially set to default as false since the consent banner is being set to pop up when users first visit the website as cookie_consent being null, therefore, there is no need to set the showCookieBanner as true. 18 const storedShowCookieBanner = getLocalStorage<boolean>( 19 "showCookieBanner", 20 false 21 ); 22 23 if (storedConsent === null) { 24 setCookieConsent(null); 25 setShowCookieBanner(true); 26 } else { 27 setCookieConsent(storedConsent); 28 29 if (typeof window !== "undefined" && window.gtag) { 30 const consentStatus = storedConsent ? "granted" : "denied"; 31 32 window.gtag("consent", "update", { 33 analytics_storage: consentStatus, 34 }); 35 } 36 } 37 38 setShowCookieBanner(storedShowCookieBanner); 39 }, []); 40 41 const handleConsent = (consent: boolean) => { 42 setCookieConsent(consent); 43 setLocalStorage("cookie_consent", consent); 44 setShowCookieBanner(false); 45 setLocalStorage("showCookieBanner", false); 46 47 if (typeof window !== "undefined" && window.gtag) { 48 const consentStatus = consent ? "granted" : "denied"; 49 50 window.gtag("consent", "update", { 51 analytics_storage: consentStatus, 52 }); 53 } 54 }; 55 56 if (cookieConsent !== null && !showCookieBanner) { 57 return null; 58 } 59 60 return ( 61 <div 62 className={`fixed w-screen z-50 bottom-0 left-0 bg-slate-400/20 backdrop-blur-md justify-center md:justify-end items-center ${ 63 showCookieBanner ? "visible" : "hidden" 64 }`} 65 > 66 <div className="flex flex-col md:flex-row justify-center items-center p-10 gap-6"> 67 <p>This site uses cookies:</p> 68 <div className="flex justify-center items-center gap-3"> 69 <button onClick={() => handleConsent(false)}>Decline</button> 70 <button onClick={() => handleConsent(true)}>Accept</button> 71 </div> 72 </div> 73 </div> 74 ); 75}

2.3 Import CookieBanner Component into the Root Layout

In my app, I imported the CookieBanner component into my root layout so that whatever page the user is on when they first visit the website, the consent banner will show up to let them know that I use cookies for my website and whether they accept or reject the trackings when there is no consent set up yet.

1export default function RootLayout({ 2 children, 3}: Readonly<{ 4 children: React.ReactNode; 5}>) { 6 return ( 7 <html lang="en"> 8 <body> 9 <main>{children}</main> 10 <CookieBanner /> 11 </body> 12 </html> 13 ); 14}

3. Integrating Google Analytics with User Consent

Once you have implemented the cookie consent banner, the next step is to integrate Google Analytics into your Next.js project. The key part of this integration is making sure that Google Analytics only starts tracking data after the user has granted consent for cookies. Below, I will show you how to implement the Google Analytics tracking script in a Next.js component that checks the user's consent status and handles the page view tracking accordingly.

3.1 Google Analytics Component

First, create a GoogleAnalytics.tsx component that will handle the loading of the Google Analytics script and the tracking of page views. This component will also respect the user's consent, ensuring that no tracking occurs unless they have opted in. The tracking is also set to "denied" as default to ensure that we only start tracking after the user accepts consent.

Here is an example of the GoogleAnalytics.tsx component:

1"use client"; 2 3import { usePathname, useSearchParams } from "next/navigation"; 4import Script from "next/script"; 5import { useEffect, useState } from "react"; 6 7declare global { 8 interface Window { 9 gtag: any; 10 } 11} 12 13export default function GoogleAnalytics({ 14 GA_MEASUREMENT_ID, 15}: { 16 GA_MEASUREMENT_ID: string; 17}) { 18 const pathname = usePathname(); 19 const searchParams = useSearchParams(); 20 const [cookieConsent, setCookieConsent] = useState<boolean | null>(null); 21 22 useEffect(() => { 23 // Retrieve cookie consent from localStorage 24 const storedCookieConsent = localStorage.getItem("cookie_consent"); 25 const consent = 26 storedCookieConsent !== null ? storedCookieConsent === "true" : null; 27 setCookieConsent(consent); 28 29 // If cookie consent is granted, enable Google Analytics tracking. No tracking if consent === null or "denied" 30 if (typeof window !== "undefined" && window.gtag) { 31 const consentStatus = 32 consent === null ? "denied" : consent ? "granted" : "denied"; 33 34 // Initialize Google Analytics with the current consent status 35 window.gtag("consent", "default", { 36 analytics_storage: consentStatus, 37 }); 38 39 // Track page view if consent is granted 40 if (consent === true) { 41 const url = 42 pathname + 43 (searchParams.toString() ? `?${searchParams.toString()}` : ""); 44 window.gtag("config", GA_MEASUREMENT_ID, { 45 page_path: url, 46 }); 47 } 48 } else { 49 console.error("gtag is not defined"); 50 } 51 }, [pathname, searchParams, GA_MEASUREMENT_ID, cookieConsent]); // Ensure it runs when the consent changes 52 53 return ( 54 <> 55 <Script 56 strategy="afterInteractive" 57 src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`} 58 /> 59 {/* Initialize Google Analytics with the specified measurement ID */} 60 <Script 61 id="google-analytics" 62 strategy="afterInteractive" 63 dangerouslySetInnerHTML={{ 64 __html: ` 65 window.dataLayer = window.dataLayer || [] 66 function gtag(){dataLayer.push(arguments);} 67 gtag('js', new Date()); 68 69 // Set the default consent state based on current consent 70 gtag('consent', 'default', { 71 'analytics_storage': '${ 72 cookieConsent === null 73 ? "denied" 74 : cookieConsent 75 ? "granted" 76 : "denied" 77 }' 78 }); 79 80 gtag('config', '${GA_MEASUREMENT_ID}', { 81 page_path: window.location.pathname, 82 }); 83 `, 84 }} 85 /> 86 </> 87 ); 88}

3.2 Import Google Analytics Component into the Root Layout

In this step, the Google Analytics component is imported into the RootLayout of your Next.js 14 project. The GA_MEASUREMENT_ID is retrieved from an environment variable that we copy and pasted from the step 1.5 above and passed as a prop to the GoogleAnalytics component. The component is wrapped in a "Suspense" tag with a null fallback to ensure that the Google Analytics script is loaded asynchronously, without blocking the main content. This setup is placed before the "body" tag to ensure proper initialization of analytics tracking on page load. By using an environment variable, you can securely manage and update your Google Analytics ID.

1export default function RootLayout({ 2 children, 3}: Readonly<{ 4 children: React.ReactNode; 5}>) { 6 return ( 7 <html lang="en"> 8 <Suspense fallback={null}> 9 <GoogleAnalytics 10 GA_MEASUREMENT_ID={process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID!} 11 /> 12 </Suspense> 13 <body> 14 <main>{children}</main> 15 <CookieBanner /> 16 </body> 17 </html> 18 ); 19}

Conclusion

To wrap up, integrating Google Analytics into your Next.js 14 project is not just about tracking user behavior; it's also about fostering trust through transparency. By setting up a consent cookie banner and prioritizing user privacy, you can create a more ethical data collection process while adhering to privacy laws. It's rewarding to know that your website is not only optimized with insights but that you're also giving users the control over their data. As a developer, this approach aligns with my values, and I hope this guide helps you implement Google Analytics in a way that respects both user experience and privacy.

Let me know about your thoughts! Leave me a comment :)

Write a Comment

*Please log in or sign up to post a comment
or