If you searched for "react cookie consent," you probably want two things: a banner that asks visitors for permission, and the confidence that you are actually compliant. Here is the catch most tutorials skip. The banner is the easy 10 percent. The part that keeps you compliant, and out of a lawsuit, is blocking tracking scripts until the visitor agrees. Most popular React consent libraries never do that. They show a banner, store a true or false, and leave every tracker firing.
Key Takeaways
- 01Cookie consent in React isn't about React. It's about blocking non-essential trackers until a visitor opts in (GDPR) or honoring their opt-out and GPC signal (US state laws).
- 02The popular react-cookie-consent library shows a banner and stores a boolean, but it blocks nothing. You wire up and maintain all the gating yourself.
- 03A compliant flow needs script-blocking before consent, granular categories, region-aware logic, GPC support, and a logged decision. The banner is the easy part.
- 04@consentstack/react gives you a drop-in ConsentStack component that blocks trackers automatically, plus useConsentValue() hooks for fine-grained control.
- 05Never trust a banner. Verify any setup with a scanner that checks what actually fires before and after a visitor clicks Reject.
This guide covers what cookie consent in React really requires, why the most-downloaded library is not enough on its own, and how to add a consent layer that actually blocks trackers in a few lines with @consentstack/react. Everything here works the same in the Next.js App Router, with a short Next.js section below.
Do cookies require consent in a React app?
Yes, but not because of React. Consent rules apply to what your cookies and scripts do, not the framework rendering them. Under the EU's GDPR and ePrivacy rules, you need prior opt-in consent before loading non-essential cookies such as analytics, ads, and most third-party pixels. Under US state laws like California's CCPA, you owe visitors a way to opt out and must honor the Global Privacy Control signal. React is just the interface layer on top of those duties.
What a compliant React consent flow actually needs
The banner is the visible part. Underneath, a flow that holds up to a regulator (or a plaintiff's lawyer) needs all of this:
- Script blocking before consent. Non-essential trackers must not run until the visitor opts in. This is the single most common failure, and the hardest part to build.
- A genuine choice. Accept and Reject need equal weight. A prominent Accept next to a buried Reject is a dark pattern regulators now fine.
- Granular categories. Visitors should be able to accept analytics but decline advertising, and change their mind later.
- Region-aware logic. GDPR is opt-in. US state laws are opt-out plus GPC. The same banner should behave correctly for both visitors.
- A stored, logged decision. You need a record of who consented to what, and when, in case you are ever asked to prove it.
Notice how little of that is "a banner." That gap, especially blocking trackers before consent, is where most React setups quietly fail.
Blocking is the hard part, and the part most tools fake. We broke down how script blocking actually works separately if you want to see what real blocking looks like under the hood.
The popular approach: react-cookie-consent
Search "react cookie consent" and the top result is the react-cookie-consent npm package. It is genuinely nice for what it is: a small, customizable banner bar. A minimal setup looks like this:
import CookieConsent from "react-cookie-consent"
export function CookieBanner() {
return (
<CookieConsent
enableDeclineButton
onAccept={() => loadAnalytics()}
onDecline={() => {}}
>
This website uses cookies to improve your experience.
</CookieConsent>
)
}That renders a banner and remembers the choice in a cookie. But look at what it does not do: it never blocks anything. Your analytics and pixels still load on page one unless you personally gate every script behind that onAccept callback. It has no consent categories, no region awareness, no GPC handling, and no server-side log of the decision. You are left hand-wiring the 90 percent that actually matters.
This is the trap the ConsentStack scanner catches most often: a real cookie banner on the page, and analytics or ad trackers firing before anyone clicks a thing. The banner makes a site look compliant while it leaks data anyway.
Building it yourself
You can absolutely build the whole thing by hand: a context provider for consent state, conditional loading for every script, a preferences modal, geolocation to tell GDPR from CCPA, a GPC listener, and an endpoint to log decisions. It is a real amount of code, and it is code that has nothing to do with your product. Every framework upgrade and every new law becomes your maintenance problem.
For a side project, fine. For anything real, you are rebuilding a consent management platform. This is the point where most developers reach for a purpose-built one.
The drop-in approach: @consentstack/react
@consentstack/react wraps the ConsentStack SDK in React hooks and components. It renders the banner, blocks non-essential scripts until consent, handles region and GPC logic, and logs decisions, so the whole checklist above is handled for you. Install it:
npm install @consentstack/react1. Add the banner
Drop the component in once, at the root of your app. It loads the SDK, shows the banner, and starts blocking non-essential trackers automatically.
import { ConsentStack } from "@consentstack/react"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<ConsentStack siteKey="pk_your_site_key" />
</body>
</html>
)
}Your siteKey comes from the ConsentStack dashboard. That is the entire baseline setup: a banner that blocks trackers before consent, respects the visitor's region, and honors GPC, with no gating code from you.
2. Gate a script on consent
When you want to load something only after consent, for example a marketing pixel, use the useConsentValue hook. It returns a boolean for one category and only re-renders when that category changes.
"use client"
import { useConsentValue } from "@consentstack/react"
export function MarketingPixel() {
const marketingAllowed = useConsentValue("marketing")
if (!marketingAllowed) return null
return (
<script
async
src="https://connect.facebook.net/en_US/fbevents.js"
/>
)
}For the hooks to work, wrap your app in ConsentStackProvider instead of the bare component:
import { ConsentStackProvider } from "@consentstack/react"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<ConsentStackProvider siteKey="pk_your_site_key">
{children}
</ConsentStackProvider>
</body>
</html>
)
}3. Read consent state
The useConsent hook gives you the full picture: effective consent per category, the visitor's region, whether they have made an explicit decision yet, and a setConsent function to update it programmatically.
"use client"
import { useConsent } from "@consentstack/react"
export function ConsentStatus() {
const { consent, region, hasDecision, isReady } = useConsent()
if (!isReady) return null
return (
<p>
Region: {region}, analytics:{" "}
{consent?.analytics ? "granted" : "denied"}
</p>
)
}consent is the visitor's effective consent, and it is populated from your consent model even before they choose. To tell whether someone has actually made a decision, read hasDecision. It is the difference between defaulted and chosen.
4. Let visitors change their mind
Regulations require an easy way to reopen and change consent. Drop PrivacyChoicesButton in your footer. It opens the preferences panel, and it switches its own label to "Do Not Sell My Personal Information" for California visitors automatically.
"use client"
import { PrivacyChoicesButton } from "@consentstack/react"
export function Footer() {
return (
<footer>
{/* ...your footer links... */}
<PrivacyChoicesButton className="text-sm text-gray-500" />
</footer>
)
}Cookie consent in a Next.js app
Everything above works in the Next.js App Router. The provider and hooks are client components (they carry the "use client" directive), so put ConsentStackProvider in your root layout and it will wrap your server-rendered pages through the children prop. You only need "use client" on the components that call the hooks, not your whole tree. The same rule applies to analytics: do not drop a raw Google Analytics or GTM script into your layout and hope a cookie flag hides it. Gate it with useConsentValue so it never loads until the visitor opts in. A cookies-next boolean or a hidden script tag is not blocking, because the script has already run.
Verify it actually works
However you add consent, in React or anywhere else, do not take the banner's word for it. The only real test is what fires before and after a visitor makes a choice. Run your site through the free ConsentStack scanner: it loads your page, clicks Reject, clicks Accept, and reports exactly which trackers ran when. If something fires before consent, your banner is decorative.
See what your React app does before consent
Run your site through the free compliance scanner. No signup, no sales call. You will see every tracker that fires before and after a visitor chooses.
React consent options compared
| Capability | react-cookie-consent | Build it yourself | @consentstack/react |
|---|---|---|---|
| Shows a banner | Yes | Yes | Yes |
| Blocks trackers before consent | No, manual | You build it | Yes, automatic |
| Granular categories | No | You build it | Yes |
| Region-aware (GDPR vs US) | No | You build it | Yes |
| Honors GPC signal | No | You build it | Yes |
| Logs the consent decision | No | You build it | Yes |
| Ongoing maintenance | Yours | All yours | Handled for you |
| Price | Free (MIT) | Your dev time | Free plan, paid from $29/mo |
react-cookie-consent is a fine choice when you genuinely only need a banner and you will handle blocking yourself. The moment you care about actually not leaking data, across regions, without babysitting it, a purpose-built consent layer is less code and less risk.
Frequently asked questions
No. react-cookie-consent renders a banner and stores the visitor's choice in a cookie, but it does not block or unblock any scripts on its own. You have to gate every tracker behind its onAccept callback yourself. Skip that, and trackers keep firing no matter what the visitor clicks.
Prevent non-essential scripts from executing until consent is given, rather than just hiding a banner. Either wrap every tracker in a consent check like useConsentValue('analytics') and render nothing until it returns true, or use a consent layer that blocks scripts automatically before they load. Hiding a script tag after it has already run does not count.
The same way as any React app: put a consent provider in your root layout. With @consentstack/react, ConsentStackProvider (or the drop-in ConsentStack component) goes in app/layout.tsx. It is a client component, so it wraps your server-rendered pages through the children prop without turning your whole app into client components.
In the EU, yes. Google Analytics sets non-essential cookies and processes personal data, so GDPR requires opt-in consent before it loads. Consent Mode helps but does not remove the consent requirement. In the US, you generally owe visitors an opt-out and must honor the GPC signal rather than ask up front.
The package is open source under the MIT license, and there is a free ConsentStack plan for low-traffic sites. Paid plans start at $29/mo and add higher visitor limits and branding removal. You install the same npm package either way; the plan is tied to your account's siteKey.
Ship a React banner that actually blocks trackers
Create a free ConsentStack account, drop in @consentstack/react, and get a consent layer that blocks trackers before consent, handles GDPR and US rules, and installs in minutes.
Related Posts
How Cookie Consent Script Blocking Actually Works (And Why Most CMPs Fake It)
Most CMPs display a banner while tracking scripts fire freely. A technical breakdown of the three script blocking approaches and why only parse-time blocking actually works.
How Cookie Consent Banners Destroy Your Core Web Vitals (And How to Fix It)
OneTrust jumps LCP from 1.4s to 3.6s. CookieYes hits 6.5s on mobile. Real performance data on how consent banners tank Core Web Vitals and what to do about it.