﻿# TanStack Start - Docs

Copy page

# TanStack Start - Docs

This tutorial shows how to integrate PostHog with a [TanStack Start](https://tanstack.com/start) app for both client-side and server-side analytics.

## Installation

Install the required packages:

Terminal

PostHog AI

```bash
npm install @posthog/react posthog-node
```

-   `@posthog/react` - React package for our [JS Web SDK](/docs/libraries/js.md) for client-side usage
-   `posthog-node` - PostHog [Node.js SDK](/docs/libraries/node.md) for server-side event capture

## Identifying users

> **Identifying users is required.** Call `posthog.identify('your-user-id')` after login to link events to a known user. This is what connects frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), and [error tracking](/docs/error-tracking.md) to the same person — and lets backend events link back too.
>
> Use a stable ID from your auth system when possible, not an email or display name. Send those as person properties instead. If your app has no other stable key, email works as a fallback if they are unique. Never a shared literal like `"anonymous"` or `"user"`, which pools many people onto one person and corrupts their data. When no ID is available at all, skip the identify and retain the anonymous distinct ID that's automatically assigned.
>
> Call `posthog.reset()` on logout, so the next person to use the browser doesn't inherit the last one's identity.
>
> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up.

If your app calls your own backend, `tracing_headers` adds `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` to matching `fetch` and `XMLHttpRequest` requests. This lets server-side SDKs link backend events, errors, and LLM traces back to frontend sessions and replays. Use hostnames only, without protocols or paths.

JavaScript

PostHog AI

```javascript
posthog.init('<ph_project_token>', {
  api_host: 'https://us.i.posthog.com',
  // Optional: send PostHog session/user context to your backend
  tracing_headers: ['api.example.com'],
})
```

This works in local development too, but match on the hostname alone: use `'localhost'`, not `'localhost:3000'`. Ports are never part of a hostname, so a value with one in it never matches anything. `localhost` and `127.0.0.1` are also different hostnames — use whichever your app actually calls.

Tracing headers help you attribute events across front and backend consistently. When this isn't available, use your server-side stable IDs to deduce the matching `distinctId`, and pass it in when capturing the event.

## Initialize PostHog on the client

Wrap your app with `PostHogProvider` in your root route with your project token, host, and other options.

PostHog AI

```
import CspAllowancesCallout from "../_snippets/csp-allowances-callout.mdx"
<CspAllowancesCallout />tsx file=src/routes/__root.tsx
// src/routes/__root.tsx
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { PostHogProvider } from '@posthog/react'
export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
    ],
  }),
  shellComponent: RootDocument,
})
function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <PostHogProvider
          apiKey="<ph_project_token>"
          options={{
            api_host: 'https://us.i.posthog.com',
            defaults: '2026-05-30',
            capture_exceptions: true
          }}
        >
          {children}
        </PostHogProvider>
        <Scripts />
      </body>
    </html>
  )
}
```

Once the provider is in place, PostHog automatically captures pageviews, sessions, and web vitals.

## Capture events on the client

Use the `usePostHog` hook from `@posthog/react` in any component to capture custom events:

src/routes/checkout.tsx

PostHog AI

```jsx
import { usePostHog } from '@posthog/react'
function CheckoutButton({ orderId, total }: { orderId: string; total: number }) {
  const posthog = usePostHog()
  const handleClick = () => {
    posthog.capture('checkout_started', {
      order_id: orderId,
      total: total,
    })
  }
  return <button onClick={handleClick}>Checkout</button>
}
```

### Identify users

Call `posthog.identify()` when a user logs in to link their events to a user ID:

TSX

PostHog AI

```jsx
import { usePostHog } from '@posthog/react'
function LoginForm() {
  const posthog = usePostHog()
  const handleLogin = async (userId: string, email: string) => {
    // ... your login logic
    posthog.identify(userId, {
      email: email,
    })
    posthog.capture('user_logged_in')
  }
}
```

Call `posthog.reset()` on logout to clear the identified user.

## Initialize PostHog on the server

Create a server-side PostHog client using `posthog-node`. Use a singleton pattern so you reuse the same client across requests:

src/utils/posthog-server.ts

PostHog AI

```typescript
// src/utils/posthog-server.ts
import { PostHog } from 'posthog-node'
let posthogClient: PostHog | null = null
export function getPostHogClient() {
  if (!posthogClient) {
    posthogClient = new PostHog(
      '<ph_project_token>',
      {
        host: 'https://us.i.posthog.com',
        flushAt: 1,
        flushInterval: 0,
      },
    )
  }
  return posthogClient
}
```

## Capture events on the server

Use the server client in TanStack Start API routes to capture events server-side. Server-side capture is useful for tracking events that shouldn't be spoofable from the client, like purchases or authentication:

src/routes/api/checkout.ts

PostHog AI

```typescript
// src/routes/api/checkout.ts
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import { getPostHogClient } from '../../utils/posthog-server'
export const Route = createFileRoute('/api/checkout')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const body = await request.json()
        const posthog = getPostHogClient()
        posthog.capture({
          distinctId: body.userId,
          event: 'item_purchased',
          properties: {
            item_id: body.itemId,
            price: body.price,
            source: 'api',
          },
        })
        return json({ success: true })
      },
    },
  },
})
```

The server-side `capture` call requires a `distinctId` (the user identifier), an `event` name, and optional `properties`.

## Next steps

Installing the JS Web SDK and Node SDK means all of their functionality is available in your TanStack Start project. To learn more about this, have a look at our [JS Web SDK docs](/docs/libraries/js/usage.md) and [Node SDK docs](/docs/libraries/node.md).

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be bet