Cookie Policy Popups in headless WordPress with Next.js 14 and WPGraphQL 🍪

Francis Agulto Avatar

·

Cookie policies are guidelines and rules that govern how cookies are used on websites to respect user privacy and comply with legal regulations. In the context of headless WordPress, which involves a decoupled architecture where the frontend is separate from the WordPress backend, implementing cookie policies and a popup banner involves a few considerations and steps.

In this article, we will take a look at how to implement cookie policy popups in headless using Next.js 13/14 with two methods. The first one will only use the cookies-next npm package with full customization and WPGraphQL.  The second will use a 3rd party service called Cookiebot.

Prerequisites

To benefit from this article, you will need to have a basic understanding of Next.js 14, headless WordPress, WPGraphQL, Tailwind styles, and 3rd party script services.

If you need a more basic tutorial, please reference my article on the subject matter here.

Before moving forward, we will be looking at the files in this example that I made here in my GitHub repo.  If you would rather spin up your own and just copy and paste the code blocks into yours, feel free to do so as well.

Laws like the EU’s General Data Protection Regulation (GDPR) and the California Consumer Privacy Act (CCPA) have strict rules about cookie usage. They require clear consent from users before any non-essential cookies are stored on their devices.

Websites must inform users about the cookies they use, what data they track, and obtain their consent. This is typically done through a cookie consent banner.

There are essential cookies (necessary for website functionality) and non-essential cookies (like those for analytics and advertising). Only essential cookies can be set without explicit consent.

Let’s take a look at how users can consent with cookies and implement a cookie consent banner on our front-end Next.js 14 application.

Method 1:  WPGraphQL and The Cookies Next NPM Package

Let’s take a look at how users can consent with cookies and implement a cookie consent banner on our front-end Next.js 14 application without a 3rd party service.

Client-Side Cookie Components 

In this section, let’s make a basic cookie consent banner that pops up when a user visits our site for the first time.  This banner will block the user from interacting with the site until they accept the terms of usage, privacy policy, etc. 

I have a basic headless WordPress site on Next.js 14 already spun up that shows my first 5 blog posts.  Let’s take a look at the app/components/CookieConsent.jsx file:

"use client";

import { useState, useEffect } from "react";
import { hasCookie, setCookie } from "cookies-next";
import Link from "next/link";
export default function CookieConsent() {
  const [showConsent, setShowConsent] = useState(true);

  useEffect(() => {
    setShowConsent(hasCookie("localConsent"));
  }, []);

  const acceptCookie = () => {
    setShowConsent(true);
    setCookie("localConsent", "true", {});
  };

  if (showConsent) {
    return null;
  }

  return (
    <div className="fixed inset-0 bg-slate-700 bg-opacity-70 z-[5000]">
      <div className=" fixed-bottom-0 left-0 flex items-center justify-between px-4 py-8 bg-orange-400">
        <span className="text-dark text-base mr-16">
          This site uses cookies to improve the UX. By using our site you
          consent to all the TOU in accordance with our{" "}
          <Link href="/privacy">Privacy Policy</Link>
        </span>
        <button
          className="bg-blue-700 py-2 px-10 rounded "
          onClick={() => acceptCookie()}
        >
          Accept
        </button>
      </div>
    </div>
  );
}

Code language: JavaScript (javascript)

At the top of the file, we declare the “use client” directive since we are going to be doing this on the client side.  This is followed by imports that we will use in this file which are the useState and useEffect React hooks to manage state and lifecycle.  The hasCookie and setCookie are functions we import from the cookies-next npm package we installed.  As well as the Link component for client-side navigation from Next.js.

After the import statements, we define the component and make it the default export of the module.  We initialize the state variable showConsent with a default value of true which determines whether the consent banner should be displayed.

Following that, we run the useEffect hook to perform the side effect to check for the presence of a cookie named localConsent when the component mounts.  If the cookie is there, it sets showConsent to false which prevents the banner from popping up.

Next, we define a function that will be called when the user clicks the “Accept” button on the banner.  It sets showConsent to false to hide the banner and sets a cookie named localConsent to true indicating that consent has been given.

Then, we have an if statement that says if showConsent is true, the component will render nothing and hide the banner.

Lastly, the return statement renders the JSX and the actual banner on the screen with the onClick handler that accepts the cookie when clicked.

To sum this up, the CookieConsent component uses local state and effect hooks to manage the display of a cookie consent banner based on the presence of a consent cookie. It provides a message about cookie usage and allows the user to accept the terms, which should hide the banner and set a cookie to remember the choice.

When you visit localhost:3000 after running the dev server, you should see this banner popup as a new user:

Create the Declaration and Policy Page with WPGraphQL

Now, let’s get stoked and take a look at using WPGraphQL to get the Policy Page data from WordPress into Next.js.  Go to app/pricvacy-policywp/page.jsx:

import { Suspense } from "react";
import Loading from "../loading";

async function getPage() {
  const query = `
    query GetPrivacyPolicyPage {
      page(id: "/privacy-policy/", idType: URI) {
        title
        content
      }
    }
  `;

  const res = await fetch(
    `${process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT}?query=${encodeURIComponent(
      query
    )}`,
    {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      },
      cache: "no-store",
    }
  );

  const responseBody = await res.json();

  if (responseBody && responseBody.data && responseBody.data.page) {
    return responseBody.data.page;
  } else {
    throw new Error("Failed to fetch the privacy policy page");
  }
}

export default async function PrivacyPolicy() {
  const page = await getPage();

  return (
    <main>
      <nav>
        <h1>{page.title}</h1>
      </nav>

      <Suspense fallback={<Loading />}>
        <div className="card">
          <p dangerouslySetInnerHTML={{ __html: page.content }} />
        </div>
      </Suspense>
    </main>
  );
}


Code language: JavaScript (javascript)

We have a server component with our imports Suspense and LoadingSuspense comes from React and allows other components to wait for something before rendering.  It handles the loading state of async operations like data fetching.  Loading is our custom component that serves as the placeholder to let the user know an operation is in progress.

Following that, we have an async function that defines the WPGraphQL query to fetch the title and content of a page with the URI “/privacy-policy/” hard coded since this is the only page we want to route to.

We use the fetch API to make a GET request to the WPGraphQL endpoint in our environment variable.  The query is URL encoded to ensure it’s transmitted correctly over HTTP.

After the request, the JSON is parsed and checks if the data is present.

Then we have an async component called getPage that fetches the data by calling getPage when rendered with some basic error handling to throw an error if the data is not present.

 Then we return it with the JSX and its format on the page.  You should see this when you visit localhost:3000/privacy-policywp after running the dev server:

Things to Consider When Implementing Without a 3rd Party Service

Choosing this method does give you full control and customization on creating your banner and policy page with no cost for a 3rd party service.  However, you do have a bit more complexity in keeping up with compliance, testing the functionality, and no auto cookie scanning.

In this section, let’s make a cookie consent banner in our Next.js 14 app and offload it to a 3rd party service called Cookiebot.

Cookiebot

Cookiebot is a 3rd party service that supports website compliance with privacy laws like the GDPR, ePR, and CCPA. It offers an automated platform that can handle user consent, cookie monitoring, and cookie control. Cookiebot enhances websites by providing a consent banner and tracking technology that informs visitors about the use of cookies, secures their consent, and honors their privacy preferences.

By integrating with our headless website, it automatically scans and tracks all cookies and similar tracking technologies in use, providing detailed reports and ensuring that the website’s use of such technologies remains transparent and compliant with legislation. 

It also helps in maintaining an updated cookie policy and declaration, automatically reflecting any changes in the use of cookies on the website.  

When using this approach, a few factors to consider are the cost of the service, limited customization options, and integration complexity.

Cookiebot Scripts in Next.js 14

Let’s take a look at the files and scripts we will need to make this work in our application.  (Just a note, I have already created a free-tier Cookiebot account.  If you are following along step-by-step, please do so before moving forward) 

In your Cookiebot Admin UI, go to Dashboard > Configuration and you should see this screen:

This allows you to set up the UI of the banner.  We will use the default setting to show on our Next.js front end in this example.

The next page you will need to navigate to is Dashboard > Script Embeds.  You should see this page:

We will need both script tags to embed in our Next.js 14 components.

Now, let’s take a look at our first component that dynamically inserts our Cookiebot script into our site at app/components/CookieBotConsent.jsx:

"use client";

import { useEffect } from "react";

const CookieConsent = () => {
  useEffect(() => {
    const cmpScript = document.createElement("script");
    cmpScript.id = "Cookiebot";
    cmpScript.src = "https://consent.cookiebot.com/uc.js";
    cmpScript.setAttribute("data-cbid", "your-cookiebot-id");
    cmpScript.setAttribute("type", "text/javascript");
    cmpScript.setAttribute("async", "true");
    cmpScript.setAttribute("data-blockingmode", "auto");
    document.head.appendChild(cmpScript);

    return () => {
      document.head.removeChild(cmpScript);
    };
  }, []);

  return null;
};

export default CookieConsent;

Code language: JavaScript (javascript)

This file is very similar to the first section’s component as we are going to use the client-side directive for this as well as the useEffect hook from React.

We use the useEffect hook to execute side effects in the component and the empty dependency array ensures that the effect runs only once after the initial render.

The focus in this component is on the script elements.  Inside our useEffect hook, we have a variable that creates a new script element called cmpScript.  This is where we load the Cookiebot script.

Then we set all the attributes.  I won’t go over every attribute.  The main ones I will focus on are:

cmpScript.src = "https://consent.cookiebot.com/uc.js"; – This specifies the source of the script, pointing to the Cookiebot script.

cmpScript.setAttribute("data-cbid", "your-cookiebot-id"); – This sets your specific Cookiebot ID. Replace "your-cookiebot-id" with your actual Cookiebot ID.

cmpScript.setAttribute("data-blockingmode", "auto"); – This sets the blocking mode for scripts to "auto", allowing Cookiebot to manage script blocking based on user consent.

Following the running of those scripts, we append the element to the head of the document which injects the Cookiebot script into the page.

Then we run a cleanup function that removes the script element from the document head when the component unmounts.  This will prevent any unintended side effects.

We are not rendering any JSX here as we are using Cookiebot to show a UI, so we return null.

The next step is to add the CookieBotConsent component to our global layout so that it appears on all pages of our site.  In order to do this, we go to app/layout.jsx:

import "./globals.css";
import { Rubik } from "next/font/google";
import CookieBotConsent from "./components/CookieBotConsent";
import Navbar from "./components/Navbar";

const rubik = Rubik({ subsets: ["latin"] });

export const metadata = {
  title: "Headless WordPress Example",
  description: "Generated by Franly the Manly",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={rubik.className}>
        <Navbar />
        {children}
        <CookieBotConsent />
        <footer
          style={{ textAlign: "center", marginTop: "20px", fontSize: "14px" }}
        >
          © 2024 Fran Agulto Dev Stoke
        </footer>
      </body>
    </html>
  );
}


Code language: JavaScript (javascript)

The CookiebotConsent component is added under the Navbar, right before the footer.  Now, this will appear on all our pages in the entire application.

To effectively test the Cookiebot consent banner, we have a few options. However, a key requirement is a publicly accessible domain, as well as ensuring cross-domain consistency within a live production environment. It’s important to note that using localhost:3000 from the Next.js development server is not a viable option for this testing.

The 3 ways you can test the banner are:

  1. Get a free Atlas sandbox account or any host, spin up a live site with a URL 
  2. Use the preview link feature off Atlas on a feature branch
  3. Use a service like ngrok to tunnel a public domain to the localhost:3000 port

(Please refer to the documentation if you choose the ngrok setup)

Once you have chosen your method, go to your Cookiebot admin UI and navigate to Dashboard > Domains and Aliases.  You should see this page and once you click the button to add a domain, you are connected.

We now have a publicly accessible URL that Cookiebot can track and if I visit the site for the first time, the Cookie Consent banner pops up like so:

Create the Declaration and Privacy Policy Page

The next component we will look at is a page that renders the Privacy Policy from Cookiebot. Navigate to app/components/CookieBotDeclaration.jsx:


"use client";
import { useEffect } from "react";

const CookieDeclaration = () => {
  useEffect(() => {
    const cookieDeclarationScript = document.createElement("script");
    cookieDeclarationScript.id = "CookieDeclaration";
    cookieDeclarationScript.src =
      "https://consent.cookiebot.com/your-cookiebot-id/cd.js";
    cookieDeclarationScript.setAttribute("type", "text/javascript");
    cookieDeclarationScript.setAttribute("async", "true");
    document.body.appendChild(cookieDeclarationScript);

    return () => {
      document.body.removeChild(cookieDeclarationScript);
    };
  }, []);

  return null; // The script itself handles rendering the cookie declaration
};

export default CookieDeclaration;

Code language: JavaScript (javascript)

I am not going to go over the entire code block because this is identical  to the component we looked at that ran the CookieBotConsent.jsx file.  The difference is that we are just running the script to render the Privacy Policy and Declaration page that comes from it.

Lastly, we need to insert the component into our privacy-policy route segment folder which has the page.jsx file like so:

import CookieBotDeclaration from "../components/CookieBotDeclaration";
import React from "react";

const PrivacyPolicyPage = () => {
  return (
    <div>
      <h1>Privacy Policy</h1>
     
      <CookieBotDeclaration />
    </div>
  );
};

export default PrivacyPolicyPage;

Code language: JavaScript (javascript)

You should see this once you visit whatever your URL is /privacy-policy

Conclusion

Cookie Policy Popups are an important part of websites for legal compliance and user transparency/trust.  I hope you have a better understanding of how this works in headless WordPress, focusing on the frontend.

As always, stoked to hear your feedback and any questions you might have on headless WordPress! Hit us up in our Discord!