# Installation

URL: https://supapulse.app/docs/react/installation

Install @supapulse/react, add the provider to Vite, Next.js, Remix or Gatsby apps, and set the token per environment.

## Install [#install]

```bash
npm install @supapulse/core @supapulse/react
```

`@supapulse/core` is a peer dependency. React 16.8 or later is required.

## Add the provider [#add-the-provider]

Wrap your app once, as high as possible. Define the config outside the component so it isn't a new object on every render.

<Tabs items={['Vite', 'Next.js App Router', 'Next.js Pages Router', 'Remix', 'Gatsby']}>
<Tab value="Vite">

```tsx title="src/main.tsx"
import { SupapulseProvider } from '@supapulse/react';
import { createRoot } from 'react-dom/client';
import { App } from './App';

const config = { apiToken: import.meta.env.VITE_SUPAPULSE_TOKEN };

createRoot(document.getElementById('root')!).render(
  <SupapulseProvider config={config}>
    <App />
  </SupapulseProvider>,
);
```

</Tab>
<Tab value="Next.js App Router">

```tsx title="app/analytics.tsx"
'use client';

import { SupapulseProvider } from '@supapulse/react';
import type { ReactNode } from 'react';

const config = { apiToken: process.env.NEXT_PUBLIC_SUPAPULSE_TOKEN! };

export function Analytics({ children }: { children: ReactNode }) {
  return <SupapulseProvider config={config}>{children}</SupapulseProvider>;
}
```

```tsx title="app/layout.tsx"
import { Analytics } from './analytics';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Analytics>{children}</Analytics>
      </body>
    </html>
  );
}
```

</Tab>
<Tab value="Next.js Pages Router">

```tsx title="pages/_app.tsx"
import { SupapulseProvider } from '@supapulse/react';
import type { AppProps } from 'next/app';

const config = { apiToken: process.env.NEXT_PUBLIC_SUPAPULSE_TOKEN! };

export default function App({ Component, pageProps }: AppProps) {
  return (
    <SupapulseProvider config={config}>
      <Component {...pageProps} />
    </SupapulseProvider>
  );
}
```

</Tab>
<Tab value="Remix">

```tsx title="app/root.tsx"
import { SupapulseProvider } from '@supapulse/react';
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';

const config = { apiToken: 'pub_your_token' };

export default function App() {
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <SupapulseProvider config={config}>
          <Outlet />
        </SupapulseProvider>
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}
```

The public token is safe to hard-code. To read it from the environment instead, return it from the root `loader` and pass it in.

</Tab>
<Tab value="Gatsby">

```tsx title="gatsby-browser.tsx"
import { SupapulseProvider } from '@supapulse/react';
import type { GatsbyBrowser } from 'gatsby';

const config = { apiToken: process.env.GATSBY_SUPAPULSE_TOKEN! };

export const wrapRootElement: GatsbyBrowser['wrapRootElement'] = ({ element }) => (
  <SupapulseProvider config={config}>{element}</SupapulseProvider>
);
```

</Tab>
</Tabs>

## Environment variables [#environment-variables]

| Framework | Variable |
| --- | --- |
| Vite | `VITE_SUPAPULSE_TOKEN` |
| Next.js | `NEXT_PUBLIC_SUPAPULSE_TOKEN` |
| Create React App | `REACT_APP_SUPAPULSE_TOKEN` |
| Gatsby | `GATSBY_SUPAPULSE_TOKEN` |

All of these end up in the browser bundle, which is fine for the public token (`pub_…`) and never fine for the secret one.

## Check it [#check-it]

1. Add `debug: true` to the config.
2. Load the app. The console logs `[Supapulse] Initialized` and a pageview send.
3. Navigate. Each route change logs one more send.

## Common errors [#common-errors]

**`useSupapulse must be used within a SupapulseProvider`.** A component calling a hook is rendered outside the provider. Move the provider up.

**`Cannot read properties of null (reading 'event')`.** An event was sent before the tracker was ready, usually from a child's `useEffect` on mount: children's effects run before the provider's. Guard on the instance; see [hooks](https://supapulse.app/docs/react/hooks#usesupapulse).

**Two pageviews per page.** You called `usePageview()` while automatic pageviews were on. Remove it, or set `autoPageviews: false`.
