Web quickstart
Add Voidhash analytics and attribution to a website, a React app, or a Next.js app.
This quickstart starts from an existing website and ends with page views, a product event, campaign attribution, and signed-in visitors linked to your users. It takes about ten minutes.
You need a Voidhash project and its publishable key (vh_pk_…). Find it in Studio under
Settings → API keys.
Install the SDK
The web SDK is published to pkg.voidha.sh from every change, before it is released on npm. Install
the newest build:
npm install @voidhash/web@https://pkg.voidha.sh/voidhash-web/main effect@4.0.3effect is a peer dependency, and the SDK supports effect 4.0.3, so the command pins it.
For production, pin a build, so every deploy installs the same SDK. The version of an installed
build ends with -g and its short commit hash, as in 0.0.1-alpha.1-g83f7412; install that build
with https://pkg.voidha.sh/voidhash-web/83f7412, using your hash. Your lockfile then records
exactly that build.
npm install @voidhash/web installs an early preview that predates this guide. Install from
pkg.voidha.sh until the SDK is released on npm.
Add the client
Pick the setup that matches your site. Each one records page views, including client-side route changes, and campaign attribution as soon as the client initializes.
Create the client in a module that runs in the browser, and initialize it once per page:
import { createVoidhashClient } from "@voidhash/web";
export const voidhash = createVoidhashClient({
publishableKey: "vh_pk_…",
});
void voidhash.initialize();Import this module from your page's entry script. Page views of navigations made with
history.pushState, history.replaceState, or the back and forward buttons are recorded
automatically, so a single-page app needs no router integration.
Wrap your app in the provider:
import { VoidhashProvider } from "@voidhash/web/react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(
<VoidhashProvider config={{ publishableKey: import.meta.env.VITE_VOIDHASH_KEY }}>
<App />
</VoidhashProvider>,
);The provider creates the client and initializes it when it mounts. Read it anywhere below with the hooks:
import { useAnalytics } from "@voidhash/web/react";
export function UpgradeButton() {
const { track } = useAnalytics();
return <button onClick={() => void track("upgrade_clicked", { plan: "yearly" })}>Upgrade</button>;
}React Router, TanStack Router, and any other router built on the History API get page views without extra code.
In the App Router, put the provider in a client component of your own, so you can pass options that
are functions, such as onDiagnostic:
"use client";
import { VoidhashProvider } from "@voidhash/web/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<VoidhashProvider config={{ publishableKey: process.env.NEXT_PUBLIC_VOIDHASH_KEY! }}>
{children}
</VoidhashProvider>
);
}Then render it from the root layout, which stays a server component:
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}This is safe to server-render: the provider and hooks render on the server without touching the
browser, and the client initializes after hydration. Do not call initialize() at the top level of
a module, because client components also run on the server; there it rejects with an error that
says it needs a browser. Navigations with next/link and
useRouter are recorded as page views automatically.
The Pages Router works the same way: render VoidhashProvider in pages/_app.tsx.
Track an event
await voidhash.track("checkout_started", { plan: "yearly" });track() queues the event and returns; it never waits for the network. Events are sent in batches,
retried until Voidhash accepts them, and kept across page loads while the visitor is offline. Names that begin with $ are
reserved for Voidhash's own events. See Track events.
Identify signed-in users
When a visitor signs in or signs up, call identify() with your own stable user ID. Their earlier
anonymous activity, including the campaign that brought them, becomes part of that user.
await voidhash.identify(user.id, { email: user.email, name: user.name });Use the same user ID in your mobile app, and the visitor and the app user are one person. Call
signOut() when the user signs out. See Identify users.
Attribute Stripe purchases
Pass the visitor's Voidhash ID to your server when it creates a Stripe Checkout Session, and set it
as client_reference_id; for a subscription, set it on the subscription's metadata too. Voidhash
then credits the purchase, its renewals, and its refunds to the visitor and the campaign that
brought them. See Stripe revenue.
Check that events arrive
Open your site, browse a few pages, and open Activity → Analytics events in Studio. Page views appear within a few seconds. If they do not, see Troubleshooting.
Ask for consent first
If your site needs consent before analytics, start with consent pending and pass the visitor's
choice when they make it. Until then, no page views, events, or person attributes are recorded,
stored, or sent, and the campaign the visitor arrived from is kept for the browser session so it is
not lost to the banner. The SDK still keeps its anonymous ID in the browser and can evaluate feature
flags and identify a signed-in user; see
what consent covers.
const voidhash = createVoidhashClient({
analyticsConsent: "pending",
publishableKey: "vh_pk_…",
});See Analytics consent for wiring a cookie banner.