# Install FlowConsent on React

Install FlowConsent on React (Vite, Create React App, React Router): paste the snippet at the top of the <head> in index.html, then turn on SPA mode.

> Canonical: https://www.flowconsent.com/en/doc/install-react
> Last updated: 2026-10-01
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](https://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](/en/doc/cloudflare-deployment)); on Pro and Enterprise it takes one click.
- Using Next.js? Follow the [Next.js and React SPA guide](/en/doc/nextjs-integration) 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 in `public/`). 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:

```html title="index.html (Vite) or public/index.html (Create React App)"
<!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`:

```tsx title="app/root.tsx"
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

> [!WARNING]
> Do not inject the FlowConsent script from a React component, a `useEffect` or a `<head>` management library. That code only runs after the bundle has downloaded and the app has mounted: scripts already present in `index.html` will have fired by then, and prior blocking no longer works. The snippet must be hard-coded in the HTML, at the top of the `<head>`.

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](/en/doc/nextjs-integration)). With Google Tag Manager, the snippet goes **above** the GTM container, never inside it ([Google Consent Mode guide](/en/doc/google-consent-mode)).

## Verify the installation

1. Open the live app in a private window: the banner appears.
2. In the **Network** tab, no third-party request goes out before **Accept**, including after several route changes.
3. Run the [setup wizard](/en/doc/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.
4. Finish with a refusal scan from the **Compliance** tab ([compliance verification](/en/doc/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](/en/doc/mcp-server) and [FlowConsent for AI agents](/en/mcp).

## 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](/en/doc/install-vercel) and [Netlify](/en/doc/install-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**.
