In a React app, the FlowConsent snippet goes at the very top of the <head> in the app's HTML file: index.html at the project root with Vite, public/index.html with Create React App, or the Layout component in app/root.tsx with React Router in framework mode. Two steps are enough, and the second is mandatory: turn on SPA mode, because a React app navigates without reloading the page. The main trap is loading the script from a React component or a useEffect: by then it is too late.
Before you start
- An account on app.flowconsent.com (free Free plan, no credit card, up to 25,000 sessions per month).
- A banner created and deployed. On the Free plan, deployment goes through your own Cloudflare account (Cloudflare guide); on Pro and Enterprise it takes one click.
- Using Next.js? Follow the Next.js and React SPA guide instead.
Step 1: Paste the snippet into the app's HTML
Copy the snippet from Builder → Deployment → Integration, HTML / No-code tab. Then open the HTML file that acts as the entry point:
- Vite:
index.html, at the project root (not inpublic/). Vite treats it as the entry point of your application. - Create React App:
public/index.html. Create React App has been deprecated for new projects since February 2025, but existing apps work the same way.
Paste the snippet right after the opening <head> tag, before any other script:
<!doctype html>
<html lang="en">
<head>
<script>
// FlowConsent Loader - place BEFORE any other script in the <head>
window.dataLayer=window.dataLayer||[];
function gtag(){dataLayer.push(arguments);}
gtag('consent','default',{
'ad_storage':'denied',
'analytics_storage':'denied',
'ad_user_data':'denied',
'ad_personalization':'denied',
'wait_for_update':500
});
gtag('set','url_passthrough',true);
gtag('set','ads_data_redaction',true);
</script>
<!-- FlowConsent CMP -->
<script src="https://YOUR-LICENSE-CODE.consent.flowconsent.com/cookie-manager.js?code=YOUR_LICENSE_CODE"></script>
<meta charset="UTF-8" />
<title>My app</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>Do not add type="module" to the two FlowConsent tags: module scripts are deferred, while these scripts must run immediately. Rebuild and redeploy the app.
React Router in framework mode
In framework mode (React Router v7), the HTML document is rendered by the Layout component in app/root.tsx. Put the snippet first in its <head>, with the stub passed through dangerouslySetInnerHTML:
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<script
dangerouslySetInnerHTML={{
__html: `window.dataLayer=window.dataLayer||[];
function gtag(){dataLayer.push(arguments);}
gtag('consent','default',{'ad_storage':'denied','analytics_storage':'denied','ad_user_data':'denied','ad_personalization':'denied','wait_for_update':500});
gtag('set','url_passthrough',true);
gtag('set','ads_data_redaction',true);`,
}}
/>
{/* FlowConsent CMP */}
<script src="https://YOUR-LICENSE-CODE.consent.flowconsent.com/cookie-manager.js?code=YOUR_LICENSE_CODE" />
<meta charSet="utf-8" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}In declarative or data mode (with Vite), React Router has no root.tsx: use index.html as shown above.
Step 2: Turn on SPA mode (mandatory)
A React app changes pages with history.pushState, without a reload. Without SPA mode, the banner does not re-check consent on later navigations. In the Builder, open the Settings tab, turn on SPA mode and redeploy the banner. FlowConsent then hooks into pushState, replaceState and popstate on every client-side navigation.
The trap: loading FlowConsent from a component
Likewise, do not load Google Analytics or Meta Pixel in your components: declare them in Builder → Services so the banner injects them only after consent. To react to the visitor's choice in your code, listen for the flowconsent:consent event (example in the Next.js and React SPA guide). With Google Tag Manager, the snippet goes above the GTM container, never inside it (Google Consent Mode guide).
Verify the installation
- Open the live app in a private window: the banner appears.
- In the Network tab, no third-party request goes out before Accept, including after several route changes.
- Run the setup wizard (Builder → Integration): it checks that the loader is present with your license code and that the Consent Mode stub comes before every Google tag.
- Finish with a refusal scan from the Compliance tab (compliance verification).
Do it from your AI agent (MCP)
FlowConsent has an official MCP server, https://mcp.flowconsent.com, usable from Claude, ChatGPT or any MCP client, with an OAuth 2.0 connection to your FlowConsent account (no API key). The agent fetches the snippet and placement instructions (get_banner_snippet, get_setup_instructions), verifies the installation with a real scan (verify_consent_compliance) and can set up recurring monitoring (monitor_compliance). Editing index.html remains up to you, or your coding agent.
Give me my banner's snippet for my React app (Vite), tell me where to put it, then check that it is installed correctly on https://mysite.com
Learn more: MCP server and FlowConsent for AI agents.
Frequently asked questions
Do I need to install an npm package for React?
No: the banner is a standalone script that belongs in the HTML. A package imported into your bundle would run too late for prior blocking.
Is SPA mode really mandatory?
Yes, as soon as the app uses a client-side router (React Router, TanStack Router…). Without it, scripts injected after the first page are not blocked again on later navigations.
My app is hosted on Vercel or Netlify. Where does the snippet go?
Still in index.html (or app/root.tsx). The host changes nothing. See Vercel and Netlify.
The banner shows, but Google Analytics fires before the choice. Why?
A GA script is probably loaded above the snippet, or from a component. Remove it and declare Google Analytics in Builder → Services.