How to add a cookie consent banner to a React single-page app
In a React single-page app, put the snippet in the HTML shell (index.html at the root of a Vite project, public/index.html in Create React App) above your app's script, not inside a component. Mark tracking tags in the same file, and gate embeds and route-change page views with a small hook that listens to Cookiequiet's cq:consent event.Steps
Where does the snippet go in a React app?
A React single-page app has one HTML file that loads the bundle. The snippet goes in its head, above everything else. In a Vite project that file is index.html at the project root; in Create React App it is public/index.html. Create React App was deprecated by the React team in February 2025 and keeps working in maintenance mode, so the same step applies to existing apps.
html<!doctype html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><script src="https://cookiequiet.com/cq.js" data-site="%VITE_COOKIEQUIET_SITE%" data-consent-mode="1" data-auto-block="1" defer></script><title>My app</title></head><body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body></html>
Vite replaces %VITE_COOKIEQUIET_SITE% in index.html with the variable from your .env file. If the variable is not defined, Vite leaves the text as it is, so check the built file, or write the key directly. Vite's entry script is a module script, which the browser runs after parsing, in document order with deferred scripts, so cq.js placed above it runs first.
Do not bundle cq.js or inject it from a component. The banner reads its settings from its own script tag and has to run before the tags it controls, which only the HTML shell guarantees.
How do I block my tags until consent?
Marked tags in the HTML shell. Paste them under the snippet and change their opening tag:
html<script type="text/plain" data-cq-category="marketing">/* Meta Pixel base code, unchanged */</script>
Cookiequiet runs them when the category is granted and at once on later visits where the stored choice includes it. The Meta Pixel and Google Analytics 4 guides show the full tags.
Scripts your code adds at runtime. With data-auto-block="1", scripts and iframes created by JavaScript from about 80 known trackers are held until their category is granted. For anything else, gate it in your own code.
How do React components read the visitor's choice?
Cookiequiet dispatches a cq:consent event on window each time it applies a choice: a stored one on page load, a new one after a click, and every category in regions where geo-targeting does not show the banner. event.detail.categories lists the granted categories. window.Cookiequiet.get() returns the choice in force, whose c array holds the same list (with a: "geo" where no banner is shown), or null while no choice applies yet. A hook built on useSyncExternalStore:
tsx// src/consent.tsimport { useSyncExternalStore } from "react";const cq = (window as any).Cookiequiet;let granted: string[] = cq?.get()?.c ?? [];const listeners = new Set<() => void>();window.addEventListener("cq:consent", (e) => {granted = (e as CustomEvent).detail.categories;listeners.forEach((l) => l());});export function useConsent(category: string) {return useSyncExternalStore((cb) => {listeners.add(cb);return () => { listeners.delete(cb); };},() => granted.includes(category),);}
Render a YouTube player only when useConsent("marketing") is true; otherwise show a placeholder with a button that calls window.Cookiequiet.showPreferences().
What about route changes?
React Router and other client-side routers change the URL without reloading the page. The snippet runs once per full load, the choice stays applied and the banner does not reappear. Two things follow:
- Marked scripts are activated on load and after each choice. Keep them in the HTML shell, not in components rendered later.
- Route-change page views are your code's job. With React Router, run an effect on
useLocation():
tsxconst location = useLocation();const analytics = useConsent("analytics");useEffect(() => {if (analytics) sendPageView(location.pathname); // your analytics call}, [location, analytics]);
If you use Next.js, Gatsby or a framework with server rendering, follow the Next.js guide or the matching framework page instead.
How do I add the Cookie preferences link?
tsx<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>
Cookiequiet handles clicks on this link anywhere in the page, so no click handler is needed.
How do I test it?
- Run the build and preview it (
vite buildthenvite preview), then view the page source:cq.jscomes first. - Open the app in a private window and click Reject all. The cookies are
cq_consentand your own, nothing from Google or Meta. - Clear the cookies, click Accept all, reload: marked tags run. Change routes and check that the page views follow the choice.
- Open the consent log in the dashboard.
A Content Security Policy must allow https://cookiequiet.com in script-src and connect-src. Cookiequiet is free for one website with geo-targeting; Starter is €3 per website per month, with unlimited pageviews on every plan.