@interfere/vite
v11.0.3
Published
Vite build plugin and client + server initialization for Interfere.
Maintainers
Readme
Getting Started
Prerequisites
- Vite
>=5 - React
>=19
Installation
npm install @interfere/viteQuick Start (vanilla Vite SPA)
1. Add the Vite plugin
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { interfere } from "@interfere/vite/plugin";
export default defineConfig({
plugins: [react(), interfere()],
});2. Initialize the SDK
// src/main.tsx
import { init } from "@interfere/vite/init";
import { createRoot } from "react-dom/client";
import { App } from "./app";
init();
createRoot(document.getElementById("root")!).render(<App />);3. Add the provider
// src/app.tsx
import { InterfereProvider } from "@interfere/vite/provider";
export function App() {
return (
<InterfereProvider>
<YourApp />
</InterfereProvider>
);
}That's it. The provider auto-resolves the kernel created by init(); you don't have to wire useSyncExternalStore yourself.
Quick Start (TanStack Start + Nitro)
Browser events post directly to Interfere; the server's own telemetry is registered via @interfere/vite/server. Call init() (browser) and register() (server) from the file that boots your router, and put <InterfereProvider> in __root.tsx:
// src/router.tsx
import { init } from "@interfere/vite/init";
import { register } from "@interfere/vite/server";
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
if (typeof window === "undefined") {
register().catch(() => {});
} else {
init();
}
export const getRouter = () =>
createRouter({ routeTree, scrollRestoration: true });// src/routes/__root.tsx
import { InterfereProvider } from "@interfere/vite/provider";
import { Outlet } from "@tanstack/react-router";
function RootComponent() {
return (
<InterfereProvider>
<Outlet />
</InterfereProvider>
);
}The browser guard matters for TanStack Start because prerender can evaluate the router import graph on the server, while the Interfere kernel uses browser-only APIs. For an ad-blocker-resistant setup, set interfere({ apiHost: "https://in.yourapp.com" }) — a subdomain of your own site that resolves to Interfere via a DNS record.
First-Time Setup
The SDK splits its credentials by concern: a public key travels with the browser bundle (it's not a secret), and an API key stays server-side (CI / GSM / .env.local).
- Install
@interfere/viteand add the plugin (above). - Public key — copy the dashboard's regional publishable key (
interfere_pub_us_*orinterfere_pub_eu_*) into.env.localasVITE_INTERFERE_PUBLIC_KEY=.... Public by design; safe to commit to a public repo. - API key — create an Interfere API key and put the regional secret key (
interfere_secret_us_*orinterfere_secret_eu_*) in.env.localasINTERFERE_API_KEY=.... This key is what the build pipeline uses to publish release metadata. Server-side; never commit. - First build — run
bun run build. The plugin'scloseBundlehook publishes release metadata and preflight-confirms the release. After this the commit's slug is accepted at ingest. - Dev cycle —
bun run devthen exercises the bundle in your browser. Events flow with the commit's preflight-confirmed slug (the build did the work). New commit? Re-runbun run buildso the new commit's slug is preflighted.
For Vercel preview / production, set INTERFERE_API_KEY in your .env.local or via GSM (or directly as a Vercel env var). Each preview build runs the same bun run build flow in CI; no local-vs-preview divergence.
Environment Variables
| Variable | Required | Description |
| --- | --- | --- |
| VITE_INTERFERE_PUBLIC_KEY | Yes | Surface publishable key in the interfere_pub_us_* or interfere_pub_eu_* format. Stamped into the page by the plugin at build time, and reused server-side (register(), request middleware) — the plugin mirrors it into the dev server's process, so no separate server variable is needed. |
| INTERFERE_API_KEY | Yes for release metadata | Interfere secret key in the interfere_secret_us_* or interfere_secret_eu_* format. Used by the plugin at build time. Without it, release metadata is not published and the collector rejects events as COLLECTOR_RELEASE_PREFLIGHT_UNCONFIRMED. |
| INTERFERE_API_URL | No | Override the collector URL the build pipeline targets. Defaults to https://in.interfere.com. |
| VITE_INTERFERE_API_URL | No | Override the collector URL the browser kernel targets in direct mode. Proxy mode uses INTERFERE_API_URL server-side. |
| VITE_INTERFERE_COMMIT_SHA | No | Override the auto-detected commit SHA used to derive the release slug. Defaults to git rev-parse HEAD. |
| VITE_INTERFERE_FORCE_ENABLE | No | "1" / "true" flips the SDK into force-enable mode so vite dev (which sets NODE_ENV=development) emits events. Production collectors silently 202-drop force-enabled batches; safe to ship by mistake but useless. Use only in dev. |
Plugin Options
interfere({
commitSha: "abcd0000abcd0000abcd0000abcd0000abcd0000",
});| Option | Default | Description |
| --- | --- | --- |
| apiHost | — | The endpoint the browser SDK posts to. Defaults to Interfere's endpoint. Set it to a subdomain of your own site (e.g. "https://in.yourapp.com") that resolves to Interfere via a DNS record to survive ad-blockers, or to a regional endpoint. Add a CNAME pointing at the target shown in your dashboard; Interfere provisions TLS automatically. |
| commitSha | VITE_INTERFERE_COMMIT_SHA → git rev-parse HEAD | Commit SHA used to derive the deterministic release slug. |
| sourceMaps | auto | false disables build-time release metadata publishing. By default the pipeline runs when INTERFERE_API_KEY and a commit SHA are available. |
Ingest requests never carry cookies. apiHost posts to your own site's subdomain, surviving ad-blockers with no server-side component.
Migrating From 0.x
Upgrade all @interfere/* SDK packages together. Keep the env var names, but replace old int_pub_* / interfere_pk_* values with the dashboard's regional publishable key, and replace old ak_* / interfere_ak_* build keys with the regional Interfere secret key.
Identity Management
Link sessions to your authenticated users with identity.set():
import { useInterfere } from "@interfere/vite/provider";
function useInterfereIdentity() {
const { identity } = useInterfere();
const { user } = useAuthProvider();
useEffect(() => {
if (user) {
identity.set({
identifier: user.id,
name: user.name,
email: user.email,
source: { type: "clerk", name: "Clerk" },
});
} else {
identity.clear();
}
}, [user]);
return null;
}Parameters
| Field | Required | Description |
| --- | --- | --- |
| identifier | Yes | Unique user ID (your internal ID, not email) |
| source | Yes | Auth source: { type: "clerk", name: "Clerk" }, { type: "auth0", name: "Auth0" }, or { type: "custom", name: "Your Provider" } |
| name | No | Display name |
| email | No | Email address |
| avatar | No | Avatar URL |
| traits | No | Arbitrary key-value metadata (Record<string, unknown>) |
API
| Method | Description |
| --- | --- |
| identity.set(params) | Link the current session to a user. Deduplicated per session — only the first call sends a request. |
| identity.clear() | Clears the linked identity and rotates the session. Call on logout. |
| identity.get() | Returns the current IdentifyParams, or null if no identity has been set. |
Identity is automatically cleared when the SDK is closed. Call identity.clear() on logout to start a fresh anonymous session.
Consent Management
By default, all SDK features are active. To gate features behind user consent, pass a consent prop to the provider:
import { InterfereProvider } from "@interfere/vite/provider";
function App() {
return (
<InterfereProvider consent={{ analytics: true, replay: false }}>
<YourApp />
</InterfereProvider>
);
}Consent categories
| Category | Plugins | Gated? |
| --- | --- | --- |
| necessary | Error tracking | Always on |
| analytics | Page events, rage clicks, fingerprint | Yes |
| replay | Session replay | Yes |
Omitting the consent prop disables gating entirely (all features load). Passing it enables gating — only necessary plugins plus explicitly consented categories will activate.
Imperative API
Use consent.set() and consent.get() from the useInterfere hook:
const { consent } = useInterfere();
consent.set({ analytics: true, replay: true }); // selective
consent.set(); // grant all
consent.get(); // current state, or null if no gatingInitial consent via init
To set consent before React renders (avoiding any window where non-consented plugins might load), pass it to init() directly instead of using auto:
import { init } from "@interfere/vite/init";
init({ consent: { analytics: false, replay: false } });The provider's consent prop will then keep it in sync as the user updates their preferences.
What's Included
- Build metadata injection — automatic Git SHA-derived release slug
- Error tracking — automatic capture of uncaught errors and unhandled rejections
- Session replay — full visual playback of user sessions
- Page analytics — SPA-aware pageviews and UI interaction events
- Rage click detection — surface frustrated user behavior
- Consent gating — fine-grained control over which features activate
License
MIT
