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

> In Docusaurus, add the snippet as the first entry of the scripts field in docusaurus.config.js: Docusaurus prints those tags in the head of every page, before the head tags that plugins inject. If the site uses the gtag option of the classic preset, either drop the snippet's defer so Consent Mode defaults run first, or replace the plugin with a marked script.

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

## Steps

1. **Copy the snippet** In the Cookiequiet dashboard, add the website and copy the snippet with your site key, data-consent-mode="1" and data-auto-block="1".
2. **Add it to the scripts field** In docusaurus.config.js (or .ts), add an object to scripts with src set to https://cookiequiet.com/cq.js, defer: true and the three data attributes as string values.
3. **Decide what to do with gtag** If the classic preset has a gtag option, either remove defer from the Cookiequiet entry so the denied defaults run before the plugin's tags, or remove the gtag option and load Google Analytics from a marked script.
4. **Load other tags as marked scripts** Put tracking code in a file under static/js and add it to scripts with type: "text/plain" and data-cq-category, below the Cookiequiet entry.
5. **Add the Cookie preferences link** In themeConfig.footer.links, add an item whose html field holds an anchor with href="#cq-preferences" and data-cq-show-preferences.
6. **Build and test** Run npm run build and npm run serve, open the site in a private window, click Reject all and check that no _ga cookie is set. Accept all, reload and check the consent log.

## Where does the snippet go in Docusaurus?

The `scripts` field of `docusaurus.config.js` takes URLs or objects of attributes, and Docusaurus inserts the resulting `<script>` tags in the head of every page. The plugin documentation adds that the head tags plugins inject come after the scripts added by the config, so an entry here runs before analytics plugins:

```js
// docusaurus.config.js
export default {
  // ...
  scripts: [
    {
      src: "https://cookiequiet.com/cq.js",
      defer: true,
      "data-site": "YOUR_SITE_KEY",
      "data-consent-mode": "1",
      "data-auto-block": "1",
    },
  ],
};
```

Use `scripts` rather than `headTags` for this: both print to the head, but `scripts` is the field Docusaurus documents for script tags, and its object form accepts any attribute. Do not load the banner as a client module either: client modules are bundled into the app and run after it, while the banner reads its settings from its own tag.

## What about the gtag option of the classic preset?

Many Docusaurus sites set `gtag: { trackingID: "G-..." }` in `@docusaurus/preset-classic`. The plugin is active only in production builds, and it injects the Google tag as head tags: the external `gtag.js` with `async`, then an inline script that calls `gtag('config')` straight away. That inline script runs while the page is parsed, before a deferred script. Two ways to put consent first:

**Keep the plugin, use Consent Mode.** Remove `defer: true` from the Cookiequiet entry. The banner then runs where it stands, before the plugin's tags, and sets every Google consent type to denied before `gtag('config')`. Google Analytics loads with `analytics_storage` denied and sets no `_ga` cookie until the visitor accepts; Cookiequiet sends the update after each choice. The [Google Analytics 4 guide](/platforms/google-analytics-4) explains what Google receives in that state.

**Drop the plugin, load GA only after consent.** Remove the `gtag` option and add your own file as a marked script:

```js
scripts: [
  { src: "https://cookiequiet.com/cq.js", defer: true, "data-site": "YOUR_SITE_KEY", "data-consent-mode": "1", "data-auto-block": "1" },
  { src: "/js/analytics.js", type: "text/plain", "data-cq-category": "analytics" },
],
```

```js
// static/js/analytics.js
var s = document.createElement("script");
s.async = true;
s.src = "https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX";
document.head.appendChild(s);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag("js", new Date());
gtag("config", "G-XXXXXXX");
```

Cookiequiet runs the file when analytics is granted, and at once on later visits where the stored choice includes it. Nothing from Google loads before that. Any other tracker can follow the same pattern; the [guide on blocking scripts](/guides/block-scripts-until-consent) lists the markup for embeds.

## What about client-side navigation?

A Docusaurus site is a single-page app after the first load. The snippet runs once per full load and the stored choice stays applied, so the banner does not come back on each page. If you send page views yourself, a client module's `onRouteDidUpdate` runs after each route renders, and on the first render too; check consent there with `window.Cookiequiet.get()`, whose `c` array lists the granted categories, or keep the categories from the `cq:consent` event that Cookiequiet dispatches on `window` each time it applies a choice. Docusaurus notes that hash changes also trigger these lifecycles, so compare the pathname before sending.

## How do I add the Cookie preferences link?

Footer items accept an `html` field that is rendered as given:

```js
themeConfig: {
  footer: {
    links: [
      {
        title: "Legal",
        items: [
          { html: '<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>' },
        ],
      },
    ],
  },
},
```

## How do I test it?

1. Run `npm run build` then `npm run serve`: the gtag plugin does nothing in development, so test the production build.
2. View the page source: `cq.js` is the first script in the head.
3. Open the site in a private window and click **Reject all**: no `_ga` cookie, only `cq_consent`.
4. Clear the cookies, click **Accept all**, reload: `_ga` appears and the Realtime report shows the visit.
5. Check the consent log in the dashboard.

Documentation sites often serve a global audience: geo-targeting can show the banner only in regulated countries, and it is included in the free plan, which covers one website. Starter is €3 per website per month with unlimited pageviews.

## Questions

### Should I use scripts or headTags in docusaurus.config.js?

Use scripts. It is the field Docusaurus documents for script tags, its object form takes any attribute, and its tags come before the head tags plugins inject.

### Why remove defer when I keep the gtag option?

The gtag plugin adds an inline script that calls gtag config while the page is parsed. A deferred script runs later, so Google's consent defaults would arrive after the tag. Without defer, Cookiequiet sets them first.

### Does Docusaurus have a cookie consent plugin?

Not an official one. The scripts field is enough: Cookiequiet is one script tag, configured from the dashboard.

### Why does Google Analytics not run when I test locally?

The gtag plugin is only active in production builds. Run npm run build and npm run serve to test the banner and the Google tag together.

## Sources

- [Docusaurus: docusaurus.config.js (scripts, headTags, clientModules)](https://docusaurus.io/docs/api/docusaurus-config)
- [Docusaurus: Plugin lifecycle APIs (injectHtmlTags order)](https://docusaurus.io/docs/api/plugin-methods/lifecycle-apis)
- [Docusaurus: plugin-google-gtag](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-google-gtag)
- [Docusaurus source: plugin-google-gtag injectHtmlTags](https://github.com/facebook/docusaurus/blob/main/packages/docusaurus-plugin-google-gtag/src/index.ts)
- [Docusaurus: Client-side architecture (onRouteDidUpdate)](https://docusaurus.io/docs/advanced/client)
- [Docusaurus: Theme configuration (footer html items)](https://docusaurus.io/docs/api/themes/configuration)
