This page installs Togo by hand. To have your coding agent do it instead, see Install Togo with your AI agent.
Install the SDK
npm install @togohq/sdk-browser
Put your write keys in environment variables. Use the test key (togo_pkt_…) in
.env.local and preview deployments, and the live key (togo_pkl_…) in production. Both are
public, so the NEXT_PUBLIC_ prefix is correct.
# .env.local
NEXT_PUBLIC_TOGO_WRITE_KEY=togo_pkt_your_test_key
Create one client
The browser SDK remembers one person, so it belongs in the browser only. Create the client the first time browser code asks for it:
// lib/analytics.ts
'use client';
import { init, type Client } from '@togohq/sdk-browser';
let client: Client | undefined;
export function analytics(): Client {
client ??= init({ writeKey: process.env.NEXT_PUBLIC_TOGO_WRITE_KEY! });
return client;
}
init() returns at once and never throws. If you call it during server rendering it returns a
client that does nothing and logs one warning, which is why the calls below sit in effects and
event handlers.
Record page views
Start the client as early as possible — the first event carries the referrer and the campaign parameters, which are gone once the visitor navigates — and send a page view on every route change. Add this component to your root layout:
// app/togo-page-views.tsx
'use client';
import { usePathname } from 'next/navigation';
import { useEffect } from 'react';
import { analytics } from '@/lib/analytics';
export function TogoPageViews() {
const pathname = usePathname();
useEffect(() => {
analytics().track('page_viewed');
}, [pathname]);
return null;
}
// app/layout.tsx
import { TogoPageViews } from './togo-page-views';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<TogoPageViews />
{children}
</body>
</html>
);
}
Every event already carries the page’s URL, path, referrer and campaign, so page_viewed needs
no properties. The query string and #fragment are never sent.
Track what people do
Call track() where the thing happens:
'use client';
import { analytics } from '@/lib/analytics';
export function UpgradeButton() {
return (
<button onClick={() => analytics().track('upgrade_clicked', { plan: 'pro' })}>Upgrade</button>
);
}
Use event names you already have, or lowercase snake_case for new ones. Property values are
strings, numbers, booleans or null — never an email address or anything a person typed
(What never goes in an event).
Identify people at sign-in
analytics().identify(user.id); // after sign-in
analytics().reset(); // on sign-out
Use your own stable user id, the same one your server uses. Identify people when they sign in covers the details.
Send events from route handlers
Things that happen on the server — a payment confirmed, a webhook received — are sent with
@togohq/sdk-node. On Vercel and other serverless platforms, await analytics.flush() before
you return, or the platform may freeze the function with the events still queued.
// app/api/checkout/route.ts
import { init } from '@togohq/sdk-node';
const analytics = init({ writeKey: process.env.TOGO_WRITE_KEY! });
export async function POST(request: Request) {
const { userId } = await request.json();
// ... complete the checkout ...
analytics.track('checkout_completed', { plan: 'pro' }, { userId });
await analytics.flush();
return Response.json({ ok: true });
}
The server SDK names the person on every call. Node.js and serverless covers it.
Check it works
Run the app with the test key, click around, and confirm the events arrived: Check that events arrive.