> ## Documentation Index
> Fetch the complete documentation index at: https://interfere.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Vite (React)

> Install the Interfere SDK in your Vite app. One-time setup, then Interfere captures everything you ship.

`@interfere/vite` wires Interfere into your Vite build and your running app together. You set it up once: drop in the plugin and start the SDK before render. After that, Interfere records everything the app produces (errors, sessions, traces, and logs) with no sampling and no knobs to turn. It then collapses related symptoms into one problem, weighs how much it matters, and works out the cause for you.

It works for a plain single-page app and for server-rendered setups like TanStack Start. This page covers the install plus the handful of settings most teams actually touch.

<Note>
  You'll need a **surface public key** (`interfere_pub_<region>_…`) from [Surfaces](https://interfere.com/~/*/surfaces). For source-map upload and release tracking you'll also want an **Interfere API key** (`interfere_secret_<region>_…`). See [Environment variables](#environment-variables).
</Note>

## Prerequisites

* A Vite app using React `>= 19`
* Node.js `>= 20`

## Quick start

These four steps cover a single-page app. If you server-render (TanStack Start, for example), do these first, then [Server-side rendering](#server-side-rendering).

<Steps>
  <Step title="Install the packages">
    <CodeGroup>
      ```bash npm theme={null}
      npm install @interfere/vite @interfere/react
      ```

      ```bash pnpm theme={null}
      pnpm add @interfere/vite @interfere/react
      ```

      ```bash yarn theme={null}
      yarn add @interfere/vite @interfere/react
      ```

      ```bash bun theme={null}
      bun add @interfere/vite @interfere/react
      ```
    </CodeGroup>
  </Step>

  <Step title="Add the plugin to your Vite config">
    The plugin stamps your public key into the build and, with `build.sourcemap` on, uploads source maps so a production stack trace points back to your original code instead of minified output.

    ```ts vite.config.ts theme={null}
    import { interfere } from "@interfere/vite/plugin";
    import react from "@vitejs/plugin-react";
    import { defineConfig } from "vite";

    export default defineConfig({
      build: { sourcemap: true },
      plugins: [react(), interfere()],
    });
    ```
  </Step>

  <Step title="Start the SDK before render">
    Call `init()` before you mount React, so the SDK is capturing the moment your app loads.

    ```tsx src/main.tsx theme={null}
    import { init } from "@interfere/vite/init";
    import { StrictMode } from "react";
    import { createRoot } from "react-dom/client";

    import { App } from "./app";

    init();

    createRoot(document.getElementById("root")!).render(
      <StrictMode>
        <App />
      </StrictMode>
    );
    ```
  </Step>

  <Step title="Add the provider">
    Wrap your app once so components can reach the SDK through hooks.

    ```tsx src/app.tsx theme={null}
    import { InterfereProvider } from "@interfere/vite/provider";

    export function App() {
      return <InterfereProvider>{/* your routes */}</InterfereProvider>;
    }
    ```
  </Step>
</Steps>

<Check>
  That's the whole install for a single-page app. Run it, trigger an error, and it shows up in your
  dashboard within seconds, already grouped and triaged. Open your [workspace](https://interfere.com/~/*) to confirm it. (To control whether the SDK runs in
  development, see [the FAQ](#faq).)
</Check>

## Environment variables

| Variable                    | Required     | Description                                                                                                                                                                         |
| --------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VITE_INTERFERE_PUBLIC_KEY` | Yes          | Your [surface public key](https://interfere.com/~/*/surfaces) (`interfere_pub_<region>_…`, where `<region>` is `us` or `eu`). Safe to expose; the plugin stamps it into your build. |
| `INTERFERE_API_KEY`         | For releases | Interfere API key (`interfere_secret_<region>_…`). A **build-time credential** for source-map upload and release metadata.                                                          |

<Warning>
  Only `VITE_`-prefixed variables reach the browser. Keep `INTERFERE_API_KEY` out of any `VITE_`
  name so your secret never ships in the client bundle.
</Warning>

## Configuration

The defaults are meant to be left alone, so plenty of apps never open this section. When you do need it, both settings live on the same `init()` call from step 3.

### Name your app

Running more than one app on Interfere, like a storefront and an admin panel? Give each one a `serviceName` so its problems, sessions, and metrics stay attributed to it.

```ts src/main.tsx theme={null}
init({ serviceName: "@acme/storefront" });
```

<ParamField path="serviceName" default="interfere-sdk" type="string">
  A stable name for this app. Interfere uses it to keep each surface's data separate, and to
  correlate the same issue across surfaces into one problem.
</ParamField>

### Choose what's captured

Interfere captures every signal below unless you say otherwise. Switch any of them off with `plugins`, for instance to drop session replay when recordings aren't welcome:

```ts src/main.tsx theme={null}
init({ plugins: { replay: false } });
```

<ParamField path="plugins" type="object">
  Each signal can be toggled on or off. All default to on.

  <ul>
    <li><code>errors</code>: uncaught exceptions</li>
    <li><code>logs</code>: console output</li>
    <li><code>device</code>: device and browser info</li>
    <li><code>pageEvents</code>: pageviews and clicks</li>
    <li><code>rageClick</code>: rage-click detection</li>
    <li><code>replay</code>: session replay</li>
  </ul>
</ParamField>

## Identity

Sessions start out anonymous. Attach the signed-in user and a problem will show you exactly who ran into it in [Users](https://interfere.com/~/*/users), by name and email instead of a random id. Call `identity.set()` from the `useInterfere` hook once you have a user:

```tsx theme={null}
import { useInterfere } from "@interfere/vite/provider";

function SyncIdentity() {
  const { identity } = useInterfere();
  const { user } = useAuthProvider(); // Clerk, Auth0, etc.

  useEffect(() => {
    if (!user) return;
    identity.set({
      identifier: user.id,
      name: user.name,
      email: user.email,
      source: { type: "clerk", name: "Clerk" },
    });
  }, [user]);

  return null;
}
```

<ParamField path="identifier" type="string" required>
  Your internal, stable user ID. Use this rather than the email.
</ParamField>

<ParamField path="source" type="object" required>
  Where the identity came from.

  <ul>
    <li><code>type</code>: one of <code>clerk</code>, <code>auth0</code>, or <code>custom</code></li>
    <li><code>name</code>: the provider's display name, for example <code>"Clerk"</code></li>
  </ul>
</ParamField>

<ParamField path="name" type="string">
  Display name.
</ParamField>

<ParamField path="email" type="string">
  Email address.
</ParamField>

<ParamField path="avatar" type="string">
  Avatar URL.
</ParamField>

<ParamField path="traits" type="object">
  Any extra metadata you want attached to the user (`Record<string, unknown>`).
</ParamField>

<Note>
  `identity.set()` is deduplicated per session, so calling it on every render is fine. Identity
  clears automatically when the session rotates.
</Note>

## Consent

Everything captures out of the box. To tie that to a cookie banner or a privacy choice, hand the provider a `consent` object. From then on only essential capture (error tracking and logs) runs, plus whichever categories you switch on:

```tsx theme={null}
<InterfereProvider consent={{ analytics: true, replay: false }}>
  {/* your routes */}
</InterfereProvider>
```

| Category    | What it covers                        | Gateable  |
| ----------- | ------------------------------------- | --------- |
| `necessary` | Error tracking, logs                  | Always on |
| `analytics` | Page events, rage clicks, device info | Yes       |
| `replay`    | Session replay                        | Yes       |

Update consent at runtime through the same hook:

```tsx theme={null}
const { consent } = useInterfere();

consent.set({ analytics: true, replay: true }); // selective
consent.set(); // grant everything
```

Interfere works with any consent platform (c15t, CookieYes, OneTrust). Map its booleans to the categories above.

<Tip>
  To apply consent before the first render, and avoid any flash where a non-consented feature loads,
  pass it to `init({consent})` instead. The provider prop then keeps it in sync as the user changes
  their mind.
</Tip>

## Report a handled error

Uncaught errors are already covered. For one you catch and handle yourself but still want on the record, call `capture`:

```tsx theme={null}
import { capture } from "@interfere/vite/init";

try {
  await saveDraft();
} catch (error) {
  capture(error);
}
```

## Server-side rendering

If you server-render with TanStack Start (or another Nitro-based setup), do the [Quick start](#quick-start) steps, then add the pieces below. Two things change: your server errors get captured too, and browser telemetry routes through your own domain so ad-blockers and CORS don't drop it. The plugin detects an SSR framework and switches to this proxy mode for you.

<Steps>
  <Step title="Add the telemetry route">
    A catch-all route proxies browser telemetry through your origin and boots server instrumentation. For TanStack Start:

    ```tsx src/routes/api/interfere/$.tsx theme={null}
    import { handle } from "@interfere/vite/handler";
    import { register } from "@interfere/vite/server";
    import { createFileRoute } from "@tanstack/react-router";

    if (typeof window === "undefined") {
      register();
    }

    export const Route = createFileRoute("/api/interfere/$")({
      server: {
        handlers: {
          GET: ({ request }) => handle(request),
          POST: ({ request }) => handle(request),
          OPTIONS: ({ request }) => handle(request),
        },
      },
    });
    ```
  </Step>

  <Step title="Start the SDK in your client entry">
    Call `init()` before hydration, in your client entry rather than `main.tsx`.

    ```tsx src/client.tsx theme={null}
    import { init } from "@interfere/vite/init";

    init();
    // ...hydrateRoot(...)
    ```
  </Step>

  <Step title="Wrap the root route with the provider">
    ```tsx src/routes/__root.tsx theme={null}
    import { InterfereProvider } from "@interfere/vite/provider";

    function RootComponent() {
      return (
        <InterfereProvider>
          <Outlet />
        </InterfereProvider>
      );
    }
    ```
  </Step>
</Steps>

Report handled errors from the server (route handlers, server functions) with `captureError`:

```ts theme={null}
import { captureError } from "@interfere/vite/server";

try {
  await chargeCard();
} catch (error) {
  captureError(error);
  throw error;
}
```

## Other frameworks

<CardGroup cols={2}>
  <Card title="Next.js" icon="https://mintcdn.com/interfere/MgNg5VNkabABMNbR/icons/tech/nextjs/currentcolor.svg?fit=max&auto=format&n=MgNg5VNkabABMNbR&q=85&s=78138c30ab456b35f56f5dbb78504079" href="/sdk/next-js" width="40" height="40" data-path="icons/tech/nextjs/currentcolor.svg">
    `@interfere/next`: a build plugin, the ingest route, and a provider for the App Router.
  </Card>

  <Card title="NestJS" icon="https://mintcdn.com/interfere/-ZXFpoZpeu8oxyWI/icons/tech/nestjs/transparent.svg?fit=max&auto=format&n=-ZXFpoZpeu8oxyWI&q=85&s=24095f2ad2719279173814d170a854b0" href="/sdk/nestjs" width="40" height="40" data-path="icons/tech/nestjs/transparent.svg">
    `@interfere/nest`: a backend module plus `instrument.ts` for server error capture.
  </Card>
</CardGroup>

## FAQ

<AccordionGroup>
  <Accordion title="Should I keep development data out of my dashboard?">
    The Vite SDK runs whenever it's loaded, including locally. To capture only in production builds, gate it: `init({ enabled: import.meta.env.PROD })`.
  </Accordion>

  <Accordion title="Some telemetry is blocked by ad-blockers on my SPA.">
    A pure single-page app sends telemetry straight to Interfere, which some ad-blockers filter. If
    that's a concern, run it behind a small server and add the proxy route from [Server-side
    rendering](#server-side-rendering); requests then go through your own domain.
  </Accordion>

  <Accordion title="Do you handle the same issue across multiple apps?">
    Yes. Give each app its own `serviceName`. When the same issue hits more than one surface,
    Interfere correlates it into a single problem instead of a separate alert per app.
  </Accordion>

  <Accordion title="Can I ship errors only, without analytics or replay?">
    Yes. Disable the signals you don't want with `plugins`, for example `init({ plugins: { replay: false, pageEvents: false } })`. To drop browser tracing from the bundle entirely, pass `init({ tracing: false })`.
  </Accordion>
</AccordionGroup>
