PostHog
Product analytics with feature flags and session replay, on both client and server.
PostHog is an open-source product analytics platform that also offers feature flags and session replay. It supports both browser and backend tracking, so the same trakoo event definitions work on the client and the server.
When to use it
- You want product analytics that span the browser and your backend.
- You need a single tool for events, funnels, feature flags, and session replay.
- You want to self-host or keep data in a specific region.
If you only need lightweight page views, a privacy-friendly option like Pirsch or Visitors may be a better fit.
Installation
Install trakoo alongside the PostHog SDKs you need — posthog-js for the client, posthog-node for the server.
pnpm install trakoo posthog-js posthog-node
Client-side usage
Use PostHogClientProvider from trakoo/providers/client. It accepts every option from the PostHog JS SDK, so anything you can pass to posthog.init() works here.
import { createClientAnalytics } from "trakoo/client";
import { PostHogClientProvider } from "trakoo/providers/client";
import { appEvents } from "@/lib/events";
export const analytics = createClientAnalytics({
events: appEvents,
providers: [
new PostHogClientProvider({
token: import.meta.env.VITE_POSTHOG_KEY,
api_host: import.meta.env.VITE_POSTHOG_HOST,
}),
],
});
Browser instance isolation
Every PostHogClientProvider uses its own named PostHog SDK object by default.
That keeps each provider’s live configuration and calls to identify,
capture, and reset isolated from other Trakoo instances.
Most applications should omit instanceName. It is an advanced escape hatch
for deliberately selecting a PostHog named instance:
new PostHogClientProvider({
token: import.meta.env.VITE_POSTHOG_KEY,
instanceName: "marketing",
});
Reusing the same instanceName opts providers into sharing that named SDK
object. The instance name does not change PostHog’s persistence semantics:
stored identity continues to use the project token by default, or
persistence_name when you configure one. Generated instance names therefore
do not make browser identity disposable across normal navigation or reloads.
Server-side usage
Use PostHogServerProvider from trakoo/providers/server. Server analytics is stateless, so pass user context with each call.
import { createServerAnalytics } from "trakoo/server";
import { PostHogServerProvider } from "trakoo/providers/server";
import { appEvents } from "@/lib/events";
export function createRequestAnalytics() {
return createServerAnalytics({
events: appEvents,
providers: [
new PostHogServerProvider({
apiKey: process.env.POSTHOG_API_KEY!,
host: "https://us.i.posthog.com",
}),
],
});
}
import { createRequestAnalytics } from "@/lib/server-analytics";
export async function POST() {
const analytics = createRequestAnalytics();
try {
await analytics.track(
"user_signed_up",
{
plan: "pro",
},
{
userId: "user_123",
},
);
return Response.json({ ok: true });
} finally {
await analytics.shutdown();
}
}
For server pageView calls, PostHog uses context.user.userId as the distinct
ID, falls back to the email supplied in that call, and otherwise records the
page view as anonymous. Server identity is never retained between calls, so
include the relevant user context on every page view that should be attributed
to a user.
Configuration
PostHogClientProvider forwards all PostHog JS options. The keys you’ll set most often:
tokenstring
Your PostHog project API key.
stringapi_host?string
PostHog instance URL. Change this for EU or self-hosted.
stringhttps://us.i.posthog.comdebug?boolean
Log PostHog SDK activity to the console.
booleanfalseenabled?boolean
Set to false to disable the provider without removing it.
booleantrueinstanceName?string
Advanced: select a named PostHog SDK instance. Omit for an isolated Trakoo-managed instance.
stringPostHogServerProvider forwards all PostHog Node options. The common ones:
apiKeystring
Your PostHog project API key.
stringhost?string
PostHog instance URL. Change this for EU or self-hosted.
stringhttps://us.i.posthog.comflushAt?number
Number of queued events that triggers a flush.
number20flushInterval?number
Milliseconds to wait before flushing queued events.
number10000