# How to add a cookie consent banner to a Gatsby site

> In Gatsby, add the snippet to the head of every page from gatsby-ssr.js with onRenderBody and setHeadComponents, so it is in the static HTML before Gatsby's own scripts. Mark analytics and advertising tags in the same place with type="text/plain" and data-cq-category, and send route-change page views from gatsby-browser.js only when the category is granted.

Source: https://cookiequiet.com/platforms/gatsby
Published: 2026-10-21
Publisher: Cookiequiet

## Steps

1. **Copy the snippet** In the Cookiequiet dashboard, add the website and copy the snippet. It carries your site key and the data-consent-mode and data-auto-block attributes.
2. **Add it in gatsby-ssr.js** Create or open gatsby-ssr.js at the project root, export onRenderBody and call setHeadComponents with a script element for cq.js, with a key and the data attributes from the snippet.
3. **Put it first in the head** If plugins add their own head tags, export onPreRenderHTML and move the Cookiequiet element to the front with getHeadComponents and replaceHeadComponents.
4. **Mark your tags** Add analytics or advertising tags in the same onRenderBody call as script elements with type="text/plain" and data-cq-category, or replace plugins that print a tracker into the HTML with marked tags.
5. **Gate route-change tracking** In gatsby-browser.js, send page views from onRouteUpdate only when the cq:consent event has granted the category.
6. **Add the Cookie preferences link** In the footer component, render a plain anchor with href="#cq-preferences" and the data-cq-show-preferences attribute.
7. **Build and test** Run gatsby build and gatsby serve, open the site in a private window, click Reject all and check that no _ga or _fbp cookie is set. Accept all, reload and check the consent log.

## Where does the snippet go in Gatsby?

Gatsby renders every page to static HTML at build time. The server rendering APIs in `gatsby-ssr.js` let you add elements to that HTML: `onRenderBody` receives `setHeadComponents`, which adds components to the head of every page. That is where the snippet belongs, because it is then in the HTML before Gatsby's JavaScript bundles.

```jsx
// gatsby-ssr.js
import * as React from "react";

export const onRenderBody = ({ setHeadComponents }) => {
  setHeadComponents([
    <script
      key="cookiequiet"
      src="https://cookiequiet.com/cq.js"
      data-site="YOUR_SITE_KEY"
      data-consent-mode="1"
      data-auto-block="1"
      defer
    />,
  ]);
};
```

Do not use Gatsby's `<Script>` component for this. Its strategies (`post-hydrate`, `idle` and `off-main-thread`) all load after hydration, which is too late for a script that has to run before the tags it controls.

**Plugins that add head tags.** If a plugin also calls `setHeadComponents`, export `onPreRenderHTML` to put the snippet first. Gatsby documents this hook for reordering what other plugins added:

```jsx
export const onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
  const head = getHeadComponents();
  head.sort((a, b) => (a && a.key === "cookiequiet" ? -1 : b && b.key === "cookiequiet" ? 1 : 0));
  replaceHeadComponents(head);
};
```

When several plugins implement `onPreRenderHTML`, the last one wins, so check the built HTML.

## How do I block my own tags until consent?

**Marked tags in gatsby-ssr.js.** Add them in the same `setHeadComponents` array, after the snippet. Cookiequiet turns them into running scripts when the category is granted, and at once on later visits when the stored choice includes it:

```jsx
<script
  key="ga4-config"
  type="text/plain"
  data-cq-category="analytics"
  dangerouslySetInnerHTML={{ __html: "/* your Google tag configuration, unchanged */" }}
/>
```

The external `gtag.js` script gets the same `type` and `data-cq-category`. The [Google Analytics 4 guide](/platforms/google-analytics-4) shows both tags and the Consent Mode alternative.

**Plugins that print a tracker.** A plugin that writes a tracking script into the HTML at build time runs before any consent script can stop it, unless it is a Google tag loading under Consent Mode with denied defaults. For other trackers, remove the plugin and add the marked tag yourself. Scripts that are created later by JavaScript (a tag manager, a chat widget) are held by the auto-blocker when they come from one of the about 80 trackers it knows.

## What changes with client-side navigation?

After the first load, Gatsby navigates without reloading the page. The snippet runs once per full load and the choice stays applied, so the banner does not reappear. Two consequences:

- Marked scripts are activated when a choice is applied on load and after each choice. Keep them in `gatsby-ssr.js`, not inside page components.
- Page views on route changes are your code's job. Gatsby calls `onRouteUpdate` in `gatsby-browser.js` on every route change, including the initial load. Send the page view only when analytics is granted.

Cookiequiet dispatches a `cq:consent` event on `window` whenever it applies a choice (a stored one on load, a new one after a click, or every category in regions where the banner is not shown). Its `detail.categories` lists the granted categories:

```js
// gatsby-browser.js
let analytics = false;
window.addEventListener("cq:consent", (e) => {
  analytics = e.detail.categories.indexOf("analytics") >= 0;
});

export const onRouteUpdate = ({ location }) => {
  if (!analytics) return;
  // send your page view for location.pathname here
};
```

`window.Cookiequiet.get()` returns the choice in force (its `c` array lists the categories): the stored one, or `a: "geo"` with every category where geo-targeting shows no banner. It returns `null` while no choice applies yet.

## How do I add the Cookie preferences link?

Render a plain anchor in the footer component. A hash link does not need Gatsby's `<Link>`:

```jsx
<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>
```

## How do I test it?

1. Run `gatsby build` then `gatsby serve`: `gatsby-ssr.js` only affects the built HTML, so test the build rather than `gatsby develop`.
2. View the page source: the Cookiequiet script must be the first script in the head.
3. Open the site in a private window and click **Reject all**. The cookies should be `cq_consent` and nothing from Google or Meta.
4. Clear the cookies, click **Accept all**, reload: the marked scripts run. Navigate between pages and check that the choice holds.
5. Open the consent log in the dashboard.

The [cookie scanner](/tools/cookie-scanner) lists what the deployed site sets before consent. Cookiequiet is free for one website with geo-targeting included, and Starter is €3 per website per month with no pageview limit.

## What else is specific to Gatsby?

- **Content Security Policy.** Allow `https://cookiequiet.com` in `script-src` and `connect-src`: the banner fetches its configuration, the geo lookup and the consent records from that origin.
- **Partytown.** Keep `cq.js` on the main thread. It reads the document and controls the scripts on it.
- **Static hosting.** The banner needs no server code, so Netlify, Cloudflare Pages or any static host works.

## Questions

### Can I load Cookiequiet with Gatsby's Script component?

Not as the consent script. The Script component's strategies load after hydration, so tags in the static HTML could run first. Add the snippet with onRenderBody and setHeadComponents in gatsby-ssr.js, which puts it in the HTML of every page.

### Why does my change not show in gatsby develop?

gatsby-ssr.js shapes the HTML Gatsby renders at build time. Run gatsby build and gatsby serve, then view the page source to check that the snippet is the first script in the head.

### How do I track page views on route changes only after consent?

Listen for the cq:consent event on window, keep whether analytics is in detail.categories, and send the page view from onRouteUpdate in gatsby-browser.js only when it is. The event fires for a stored choice on load and after every new choice.

### Do I need a Gatsby plugin for Cookiequiet?

No. The snippet is one script element added in gatsby-ssr.js. There is no plugin to configure.

## Sources

- [Gatsby: Gatsby Server Rendering APIs (onRenderBody, onPreRenderHTML)](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-ssr/)
- [Gatsby: Gatsby Browser APIs (onRouteUpdate)](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-browser/)
- [Gatsby: Gatsby Script API](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-script/)
