I have just implemented a simple cookie consent banner on my website, and I want to share with you in this article how I've done it with a step-by-step guide.

The cookie consent banner is a way to inform your users how your website and the services it uses their browser cookies.

It does contain a link to your website privacy policy where they can learn in more detail how their personal data is stored and used.

Each website's policy is different, that's why you may need to change the content of the consent banner, as well as the type of action buttons your website needs.

Set up

In this article, we will use a fresh Next.js installation with TypeScript and Tailwind CSS, so it's easy for you to follow the same steps without getting lost in the project complexity.

You don't have to create a new Next.js project as long as you already have one, Luckily I already have an article that explains all about setting up a new project.

If you like to follow along, please go ahead and set up your project using the How To Set Up And Structure Your Full-Stack Next.Js Application article.

Please skip the unnecessary sections for the purpose of this article, you can read up to the Tailwind CSS section by skipping the Google Fonts section.

I went ahead and did that myself, so now I have a fresh Next.js project with TypeScript and Tailwind CSS set up and ready to go.

Creating a shared public layout

In order to include the consent banner on all our public pages, we will need to create a shared layout that wraps all the pages we want the banner to show on.

We will create a simple layout that we will apply on the homepage and our privacy policy page, but for you, you may want to use a different approach that sweets your needs better.

You can learn more about how you can do that on the Next.js Layouts docs page.

Creating the public layout component

Let's first, create a new file at components/layouts/PublicLayout.tsx and have it just add a simple header at the top and the footer at the bottom.

components/layouts/PublicLayout.tsx
TypeScript
import Link from 'next/link'
import { ReactNode } from 'react'

export const Container = ({ children }: { children: ReactNode }) => (
  <div className="w-full max-w-5xl px-2 mx-auto md:px-4">{children}</div>
)

const PublicLayout = ({ children }: { children: ReactNode }) => {
  return (
    <div className="flex flex-col min-h-screen">
      <header className="flex items-center border-b border-gray-200 h-14">
        <Container>
          <nav>
            <ul>
              <li>
                <Link href="/">
                  <a className="text-sm font-medium text-center capitalize">
                    Home
                  </a>
                </Link>
              </li>
            </ul>
          </nav>
        </Container>
      </header>
      <main className="flex-grow">
        <Container>{children}</Container>
      </main>
      <footer className="py-5 border-t border-gray-200">
        <Container>
          <nav className="my-5">
            <ul>
              <li>
                <Link href="/privacy-policy">
                  <a className="text-sm font-medium text-center capitalize">
                    Privacy Policy
                  </a>
                </Link>
              </li>
            </ul>
          </nav>
        </Container>
        <Container>
          <p className="text-sm font-medium text-center capitalize">
            copyright © {new Date().getFullYear()} all rights reserved
          </p>
        </Container>
      </footer>
    </div>
  )
}

export default PublicLayout

As you can see this is a simplified not responsive PublicLayout example component, but it will serve us very well in this article.

At the top we declared a simple React component Container which we reuse it inside our layout component, it's just a simple div.

Then we added a simple header, main, and footer sections, we used flex-col and flex-grow to have the main section take the available height, in order to push the footer to the bottom.

Applying the public layout component to our public pages

Let's go ahead and have our homepage use the PublicLayout component

pages/index.tsx
TypeScript
import Head from 'next/head'
import type { NextPage } from 'next'
import PublicLayout from '../components/layouts/PublicLayout'

const Home: NextPage = () => {
  return (
    <>
      <Head>
        <title>Next.js cookie consent banner</title>
        <meta
          name="description"
          content="A Next.js cookie consent banner with TypeScript and Tailwind CSS."
        />
      </Head>

      <PublicLayout>
        <h1 className="text-3xl font-bold font-open">
          Next.js cookie consent banner
        </h1>
      </PublicLayout>
    </>
  )
}

export default Home

We simply imported it and wrapped our content using it, now if we opened our homepage, we should see a header and a footer.

Let's create a privacy policy page

As we mentioned above we will link to the privacy policy page from the banner content. I know if you already own a website there's a high chance you already have it.

But I wanted to add it, so I can have a fully working example at the end of this article for you as a reference, as well as having a page link I can refer to.

I will go ahead and create a new file at pages/privacy-policy.tsx and a simple empty page with a title.

pages/privacy-policy.tsx
TypeScript
import Head from 'next/head'
import type { NextPage } from 'next'
import PublicLayout from '../components/layouts/PublicLayout'

const PrivacyPolicy: NextPage = () => {
  return (
    <>
      <Head>
        <title>Our Privacy Policy</title>
        <meta name="description" content="Website privacy policy page" />
      </Head>

      <PublicLayout>
        <h1 className="text-3xl font-bold font-open">
          Website privacy policy page content
        </h1>
      </PublicLayout>
    </>
  )
}

export default PrivacyPolicy

You may notice we have also wrapped its content with the PublicLayout component just like we did with the homepage.

The CookieConsent banner component

It's time to create the main component that will display the cookie consent banner to the user. but first, let's install a single dependency that we will need to manage browser cookies.

The js-cookie npm package is a handy tool to set and get our browser cookies, and we need that to remember that a user has already accepted our consent.

To install it we will use the commands npm i js-cookie and npm i --save-dev @types/js-cookie for the package types.

CookieConsent component structuring and styling

Now we are ready to start with our component structure, content, and styling, we will have a fixed banned with standard content and a single button that says "Got it".

Go ahead and create a file for our component at components/banners/CookieConsent.tsx and put the following.

components/banners/CookieConsent.tsx
TypeScript
import Link from 'next/link'
import { MouseEvent } from 'react'
import { Container } from '../layouts/PublicLayout'

const CookieConsent = () => {
  const onClick = (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()
    console.log('remember that I accept')
  }

  return (
    <section className="fixed bottom-0 left-0 w-full py-2 md:py-4">
      <Container>
        <div className="flex flex-col items-start px-5 py-3 space-y-2 bg-gray-200 md:flex-row md:space-y-0 md:items-stretch md:space-x-2">
          <div className="flex items-center flex-grow text-gray-900">
            <p className="text-sm font-medium">
              This site uses services that use cookies to deliver better
              experience and analyze traffic. You can learn more about the
              services we use at our{' '}
              <Link href="/privacy-policy">
                <a className="text-sm underline hover:text-lightAccent">
                  privacy policy
                </a>
              </Link>
              .
            </p>
          </div>
          <div className="flex items-center">
            <button
              className="p-3 text-sm font-bold text-white uppercase bg-gray-700 whitespace-nowrap"
              onClick={onClick}
            >
              Got it
            </button>
          </div>
        </div>
      </Container>
    </section>
  )
}

export default CookieConsent

I will assume that you know your way around CSS and Tailwind so I will focus on explaining the idea behind the styles instead of the actually applied styles.

We have added a simple fixed wrapper to the bottom, with a vertical padding, inside of it we have added a container which by default it has horizontal padding.

Inside the container, we added our banner saying that we use services that use the user's cookie and we give them a button to consent to it.

CookieConsent button action implementation

When the button is clicked, currently we just log to the console, but in this subsection, we will have it set a cookie with the key cookie_consent_is_true to "true" to mark a user as consented.

We will use the js-cookie package to easily do that, this package gives us two methods set and get which enable us to set a new cookie with key=value pairs.

Let's write the code first then explain it, go ahead and update the CookieConsent component to match the following changes.

components/common/popups/CookieConsent.tsx
TypeScript
import Link from 'next/link'
import Cookies from 'js-cookie'
import { MouseEvent, useCallback, useEffect, useState } from 'react'
import { Container } from '../layouts/PublicLayout'

const USER_CONSENT_COOKIE_KEY = 'cookie_consent_is_true'
const USER_CONSENT_COOKIE_EXPIRE_DATE = 365

const CookieConsent = () => {
  const [cookieConsentIsTrue, setCookieConsentIsTrue] = useState(true)

  useEffect(() => {
    const consentIsTrue = Cookies.get(USER_CONSENT_COOKIE_KEY) === 'true'
    setCookieConsentIsTrue(consentIsTrue)
  }, [])

  const onClick = (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if (!cookieConsentIsTrue) {
      Cookies.set(USER_CONSENT_COOKIE_KEY, 'true', {
        expires: USER_CONSENT_COOKIE_EXPIRE_DATE,
      })
      setCookieConsentIsTrue(true)
    }
  }

  if (cookieConsentIsTrue) {
    return null
  }

  return (
    <section className="fixed bottom-0 left-0 w-full py-2 md:py-4">
      ...
    </section>
  )
}

export default CookieConsent

For a little best practice 😁 we declared two constants USER_CONSENT_COOKIE_KEY for the cookie key and USER_CONSENT_COOKIE_EXPIRE_DATE for the cookie expires property.

Then we created a new state for our user cookie consent value cookieConsentIsTrue we set it to be true by default.

Using the useEffect hook which runs once after the component has been mounted, we check if there's a cookie with the key "cookie_consent_is_true" and value "true".

When the user clicks on the "Got it" button, we simply check if the cookieConsentIsTrue is not true, which means the cookie wasn't set before, if not then we set it with the right value and an expiration date of a single year.

Finally, before rendering our component we check if the user has already accepted, if yes we just return null which means the component won't be rendering anything.

Adding the CookieConsent to our layout

That was it for our component but sure you can extend it to whatever extent your website needs, Now it's time to add it to our public layout.

This is simply importing the component and inserting it after our footer, where you put it doesn’t matter because we give it a fixed position.

components/layouts/PublicLayout.tsx
TypeScript
// imports
import CookieConsent from '../banners/CookieConsent'

...

const PublicLayout = ({ children }: { children: ReactNode }) => {
  return (
    <div className="flex flex-col min-h-screen">
      <header className="flex items-center border-b border-gray-200 h-14">
        ...
      </header>
      <main className="flex-grow">
        <Container>{children}</Container>
      </main>
      <footer className="py-5 border-t border-gray-200">
        ...
      </footer>
      <CookieConsent />
    </div>
  )
}

export default PublicLayout

Now if we checked out our home and privacy policy pages, we should see a cookie consent banner appears.

And if we click on the "Got it" button it should disappear, even if we reload it won't show, because we set the cookie to mark the user as consented.

Conclusion

In this article, you have learned about how to add a cookie consent banner to your Next.js website with Tailwind CSS as our styling framework of choice.

Source code is available at: https://github.com/codersteps/nextjs_cookie_consent_banner.

That's it for this one, I hope it was helpful, see you in the next one 😉.