AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured, multi-step procedure for integrating the PostHog SDK into a wide range of web, mobile, and backend frameworks. It handles the detection of the project environment, installation of the appropriate SDK, initialization, user identification, and secure configuration of environment variables. The skill emphasizes following framework-specific patterns and maintaining an analytics contract, ensuring that PostHog is implemented consistently without disrupting existing application logic.
What it does
- Detects the project's framework, language, and package manager.
- Installs the correct PostHog SDK for the identified platform.
- Configures PostHog initialization and provider setup based on framework-specific references.
- Implements user identification logic for login and signup events.
- Manages environment variables for project tokens and host URLs securely.
- Verifies the integration and performs minimal cleanup.
When to use it
- When setting up PostHog in a project for the first time.
- When reviewing pull requests that require PostHog initialization.
- When adding analytics to a new application across supported frameworks.
- When migrating or updating PostHog configurations in an existing codebase.
Advantages
- Ensures consistent implementation across diverse technology stacks.
- Prevents hardcoding of sensitive project tokens and host URLs.
- Follows established framework-specific best practices and patterns.
- Minimizes the risk of breaking application boot or build processes.
- Automates the repetitive aspects of SDK setup and environment configuration.
Usage examples
- “Set up PostHog in my Next.js project.”
- The agent will detect the Next.js environment, install the PostHog SDK, configure the necessary provider, and set up environment variables in .env.local.
- “Integrate PostHog into this Android app.”
- The agent will identify the Android/Kotlin structure, initialize PostHog in the Application class, and guide the setup of local.properties for configuration.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/posthog/instrument-integration/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip instrument-integration.zip -d .agents/skills
Documentation by PostHog
Add PostHog SDK integration
Use this skill to add the PostHog SDK to an application. Use it when setting up PostHog for the first time, or reviewing PRs that need PostHog initialization. Covers SDK installation, provider setup, and basic configuration. Supports any framework or language.
Supported frameworks and languages: Next.js, React, React Router, Vue, Nuxt, TanStack Start, SvelteKit, Astro, Angular, Django, Flask, FastAPI, Laravel, PHP, Ruby on Rails, Go, Elixir, Android, iOS, Swift, Flutter, React Native, Expo, Node.js, and vanilla JavaScript.
Instructions
Follow these steps IN ORDER:
STEP 1: Analyze the codebase and detect the platform.
Look for dependency files (package.json, pubspec.yaml, Podfile, Package.swift, requirements.txt, Gemfile, composer.json, go.mod, mix.exs, etc.) to determine the framework and language.
Look for lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb, go.sum, pubspec.lock, Podfile.lock, Package.resolved, mix.lock) to determine the package manager.
Check for existing PostHog setup. If PostHog is already installed and initialized, do not modify its code. Inform the user and skip to verification.
STEP 2: Research integration. 2.1. Find the reference file below that matches the detected framework — it is the source of truth for SDK initialization, provider setup, and configuration patterns. Read it now. 2.2. If no reference matches, fall back to your general knowledge and web search. Use posthog.com/docs as the primary search source.
STEP 3: Install the PostHog SDK.
Add the PostHog SDK package for the detected platform. Do not manually edit package.json — use the package manager's install command.
STEP 4: Initialize PostHog.
Follow the framework reference for where and how to initialize. This varies significantly by framework (e.g., instrumentation-client.ts for Next.js 15.3+, AppConfig.ready() for Django, create_app() for Flask).
Set up the PostHog provider/wrapper component if the framework requires one.
STEP 5: Identify users.
Add PostHog
identify()calls on the client side during login and signup events.If both frontend and backend exist, pass the client-side session and distinct ID using
X-POSTHOG-DISTINCT-IDandX-POSTHOG-SESSION-IDheaders to the server-side code.
STEP 6: Set up environment variables.
Check if the project already has PostHog environment variables configured (e.g. in
.env,.env.local, or framework-specific env files). If valid values already exist, skip this step.If the PostHog project token is missing, use the PostHog MCP server's
projects-gettool to retrieve the project'sapi_token. If multiple projects are returned, ask the user which project to use. If the MCP server is not connected or not authenticated, ask the user for their PostHog project token instead.For the PostHog host URL: check the
projects-getMCP response for aregionfield —USmaps tohttps://us.i.posthog.com,EUmaps tohttps://eu.i.posthog.com. If the region is not available from the MCP response or from existing project configuration, ask the user: "Are you on PostHog US Cloud or EU Cloud?" Do not assume US Cloud.Write these values to the appropriate env file (e.g.
.env.localfor Next.js,.envfor others) using the framework's naming convention.Reference these environment variables in code instead of hardcoding them.
STEP 7: Verify and clean up.
Check the project for errors. Look for type checking or build scripts in package.json.
Ensure any components created were actually used.
Run any linter or prettier-like scripts found in the package.json, but ONLY on the files you have edited or created during this session. Never run formatting or linting across the entire project's codebase.
Reference files
references/EXAMPLE-next-app-router.md- next-app-router example project codereferences/EXAMPLE-next-pages-router.md- next-pages-router example project codereferences/EXAMPLE-react-react-router-6.md- react-react-router-6 example project codereferences/EXAMPLE-react-react-router-7-framework.md- react-react-router-7-framework example project codereferences/EXAMPLE-react-react-router-7-data.md- react-react-router-7-data example project codereferences/EXAMPLE-react-react-router-7-declarative.md- react-react-router-7-declarative example project codereferences/EXAMPLE-react-vite.md- react-vite example project codereferences/EXAMPLE-nuxt-3-6.md- nuxt-3-6 example project codereferences/EXAMPLE-nuxt-4.md- nuxt-4 example project codereferences/EXAMPLE-vue-3.md- vue-3 example project codereferences/EXAMPLE-react-tanstack-router-file-based.md- react-tanstack-router-file-based example project codereferences/EXAMPLE-react-tanstack-router-code-based.md- react-tanstack-router-code-based example project codereferences/EXAMPLE-tanstack-start.md- tanstack-start example project codereferences/EXAMPLE-sveltekit.md- sveltekit example project codereferences/EXAMPLE-astro-static.md- astro-static example project codereferences/EXAMPLE-astro-view-transitions.md- astro-view-transitions example project codereferences/EXAMPLE-astro-ssr.md- astro-ssr example project codereferences/EXAMPLE-astro-hybrid.md- astro-hybrid example project codereferences/EXAMPLE-angular.md- angular example project codereferences/EXAMPLE-javascript-node.md- javascript-node example project codereferences/EXAMPLE-javascript-web.md- javascript-web example project codereferences/EXAMPLE-django.md- django example project codereferences/EXAMPLE-flask.md- flask example project codereferences/EXAMPLE-fastapi.md- fastapi example project codereferences/EXAMPLE-python.md- python example project codereferences/EXAMPLE-laravel.md- laravel example project codereferences/EXAMPLE-php.md- php example project codereferences/EXAMPLE-ruby-on-rails.md- ruby-on-rails example project codereferences/EXAMPLE-ruby.md- ruby example project codereferences/EXAMPLE-android.md- android example project codereferences/EXAMPLE-swift.md- swift example project codereferences/EXAMPLE-react-native.md- react-native example project codereferences/EXAMPLE-expo.md- expo example project codereferences/next-js.md- Next.jsreferences/react.md- Reactreferences/react-router-v6.md- React router v6references/react-router-v7-framework-mode.md- React router v7 framework mode (remix v3)references/react-router-v7-data-mode.md- React router v7 data modereferences/react-router-v7-declarative-mode.md- React router v7 declarative modereferences/nuxt-js-3-6.md- Nuxt.js (v3.0 to v3.6)references/nuxt-js.md- Nuxt.jsreferences/vue-js.md- Vue.jsreferences/tanstack-start.md- Tanstack startreferences/svelte.md- Sveltereferences/astro.md- Astroreferences/angular.md- Angularreferences/js.md- JavaScript webreferences/posthog-js.md- PostHog JavaScript web SDKreferences/node.md- Node.jsreferences/posthog-node.md- PostHog Node.js SDKreferences/django.md- Djangoreferences/flask.md- Flaskreferences/python.md- Pythonreferences/posthog-python.md- PostHog python SDKreferences/dotnet.md- .netreferences/elixir.md- Elixirreferences/go.md- Goreferences/laravel.md- Laravelreferences/php.md- Phpreferences/ruby-on-rails.md- Ruby on railsreferences/ruby.md- Rubyreferences/android.md- Androidreferences/ios.md- Iosreferences/usage.md- Ios SDK usagereferences/configuration.md- Ios SDK configurationreferences/flutter.md- Flutterreferences/react-native.md- React nativereferences/identify-users.md- Identify usersreferences/COMMANDMENTS.md- Framework-specific rules the integration must follow
Each framework reference contains SDK-specific installation, initialization, and usage patterns. Find the one matching the user's stack.
Key principles
Environment variables: Always use environment variables for PostHog keys. Never hardcode them.
Minimal changes: Add PostHog code alongside existing integrations. Don't replace or restructure existing code.
Match the example: Your implementation should follow the example project's patterns as closely as possible.
Analytics contract: Treat event names, property names, and feature flag keys as part of an analytics contract. Reuse existing names and patterns found in the project. When introducing new ones, make them clear, descriptive, and consistent with existing conventions.
Files
- ATTRIBUTION.mdAdded by AutoGineer952 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md8.8 KiB
- references/COMMANDMENTS.md1.5 KiB
- references/EXAMPLE-android.md49.8 KiB
- references/EXAMPLE-angular.md23.1 KiB
- references/EXAMPLE-astro-hybrid.md27.5 KiB
- references/EXAMPLE-astro-ssr.md27.6 KiB
- references/EXAMPLE-astro-static.md19.6 KiB
- references/EXAMPLE-astro-view-transitions.md23.3 KiB
- references/EXAMPLE-django.md30.7 KiB
- references/EXAMPLE-expo.md35.1 KiB
- references/EXAMPLE-fastapi.md40.9 KiB
- references/EXAMPLE-flask.md32.4 KiB
- references/EXAMPLE-javascript-node.md9.0 KiB
- references/EXAMPLE-javascript-web.md11.8 KiB
- references/EXAMPLE-laravel.md59.3 KiB
- references/EXAMPLE-next-app-router.md17.6 KiB
- references/EXAMPLE-next-pages-router.md19.1 KiB
- references/EXAMPLE-nuxt-3-6.md22.3 KiB
- references/EXAMPLE-nuxt-4.md28.9 KiB
- references/EXAMPLE-php.md13.0 KiB
- references/EXAMPLE-python.md11.6 KiB
- references/EXAMPLE-react-native.md46.0 KiB
- references/EXAMPLE-react-react-router-6.md13.5 KiB
- references/EXAMPLE-react-react-router-7-data.md20.1 KiB
- references/EXAMPLE-react-react-router-7-declarative.md15.0 KiB
- references/EXAMPLE-react-react-router-7-framework.md32.5 KiB
- references/EXAMPLE-react-tanstack-router-code-based.md19.6 KiB
- references/EXAMPLE-react-tanstack-router-file-based.md18.3 KiB
- references/EXAMPLE-react-vite.md12.4 KiB
- references/EXAMPLE-ruby-on-rails.md38.0 KiB
- references/EXAMPLE-ruby.md10.0 KiB
- references/EXAMPLE-sveltekit.md18.6 KiB
- references/EXAMPLE-swift.md18.8 KiB
- references/EXAMPLE-tanstack-start.md29.9 KiB
- references/EXAMPLE-vue-3.md17.9 KiB
- references/android.md33.3 KiB
- references/angular.md16.5 KiB
- references/astro.md15.8 KiB
- references/configuration.md17.2 KiB
- references/django.md14.9 KiB
- references/dotnet.md25.8 KiB
- references/elixir.md17.6 KiB
- references/flask.md8.1 KiB
- references/flutter.md42.8 KiB
- references/go.md20.1 KiB
- references/identify-users.md12.2 KiB
- references/ios.md8.0 KiB
- references/js.md16.1 KiB
- references/laravel.md13.1 KiB
- references/next-js.md18.3 KiB
- references/node.md45.6 KiB
- references/nuxt-js-3-6.md11.4 KiB
- references/nuxt-js.md15.1 KiB
- references/php.md23.7 KiB
- references/posthog-js.md51.6 KiB
- references/posthog-node.md33.9 KiB
- references/posthog-python.md69.3 KiB
- references/python.md52.2 KiB
- references/react-native.md59.9 KiB
- references/react-router-v6.md13.6 KiB
- references/react-router-v7-data-mode.md13.5 KiB
- references/react-router-v7-declarative-mode.md13.7 KiB
- references/react-router-v7-framework-mode.md20.2 KiB
- references/react.md21.9 KiB
- references/ruby-on-rails.md19.7 KiB
- references/ruby.md25.5 KiB
- references/svelte.md14.6 KiB
- references/tanstack-start.md12.8 KiB
- references/usage.md29.2 KiB
- references/vue-js.md13.4 KiB
references/react-router-v7-framework-mode.md
AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
React Router V7 framework mode (Remix V3)
Canonical URL: https://posthog.com/docs/libraries/react-router/react-router-v7-framework-mode
Cloud region: This page uses US Cloud hosts. For an EU Cloud project, use
https://eu.i.posthog.cominstead ofhttps://us.i.posthog.com(ingestion host), and usehttps://eu.posthog.cominstead ofhttps://us.posthog.comorhttps://app.posthog.com(app host for links).
On this page
Install client-side SDKs
Add your environment variables
Add the PostHogProvider to your app
Verify client-side events are captured
Access PostHog methods
Identify your user
Create an error boundary
Tracking element visibility
Install server-side SDKs
Create a server-side middleware
Verify server-side events are captured
Next steps
This guide walks you through setting up PostHog for React Router V7 in framework mode. If you're using React Router in another mode, find the guide for that mode in the React Router page. If you're using React with another framework, go to the React integration guide.
1
Install client-side SDKs
Required
First, you'll need to install
posthog-jsand@posthog/reactusing your package manager. These packages allow you to capture client-side events.npm
npm install --save posthog-js @posthog/reactYarn
yarn add posthog-js @posthog/reactpnpm
pnpm add posthog-js @posthog/reactBun
bun add posthog-js @posthog/reactIf your site sets a Content-Security-Policy, it needs to allow PostHog. This applies to the snippet and to package installs alike: the SDK lazy-loads extra bundles (session replay, surveys) from PostHog's CDN, and sends events to the ingestion host. PostHog serves from subdomains of
posthog.comthat change over time, so allow the wildcard:script-src 'self' https://*.posthog.com; connect-src 'self' https://*.posthog.com; worker-src 'self' blob: data:;script-srccovers the snippet and the lazy-loaded bundles,connect-srccovers event ingestion and feature flags, andworker-srccovers session replay. The toolbar needs a few more, or use a reverse proxy so everything is first-party. Failing to do so causes silent failures wherecaptureandidentifycalls never send, so the integration looks complete while zero events arrive. Rememberconnect-srcfalls back todefault-src, sodefault-src 'self'blocks event delivery even when the script itself is bundled.In framework mode, you'll also need to set
posthog-jsand@posthog/reactas external packages in yourvite.config.tsfile to avoid SSR errors.vite.config.ts
// ... imports export default defineConfig({ plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], ssr: { noExternal: ['posthog-js', '@posthog/react'] } });2
Add your environment variables
Required
Add your environment variables to your
.env.localfile and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in your project settings. If you're using Vite, prefixing variable names withVITE_ensures they are accessible in the frontend..env.local
VITE_POSTHOG_PROJECT_TOKEN=<ph_project_token> VITE_POSTHOG_HOST=https://us.i.posthog.com3
Add the PostHogProvider to your app
Required
In framework mode, your app enters from the
app/entry.client.tsxfile. In this file, you'll need to initialize the PostHog SDK and pass it to your app through thePostHogProvidercontext.app/entry.client.tsx
import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; import posthog from 'posthog-js'; import { PostHogProvider } from '@posthog/react' posthog.init(import.meta.env.VITE_POSTHOG_PROJECT_TOKEN, { api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-08-30', tracing_headers: [ window.location.hostname, 'localhost' ], }); startTransition(() => { hydrateRoot( document, {/* Pass PostHog client through PostHogProvider */} <PostHogProvider client={posthog}> <StrictMode> <HydratedRouter /> </StrictMode> </PostHogProvider>, ); });To help PostHog track your user sessions across the client and server, you'll need to add the
tracing_headers: ['your-backend-hostname1.com', 'your-backend-hostname2.com', ...]option to your PostHog initialization. This adds theX-POSTHOG-DISTINCT-IDandX-POSTHOG-SESSION-IDheaders to requests sent to the configured hostnames, which we'll later use on the server-side.TypeError: Cannot read properties of undefined
If you see the error
TypeError: Cannot read properties of undefined (reading '...')this is likely because you tried to call a posthog function when posthog was not initialized (such as during the initial render). On purpose, we still render the children even if PostHog is not initialized so that your app still loads even if PostHog can't load.To fix this error, add a check that posthog has been initialized such as:
useEffect(() => { posthog?.capture('test') // using optional chaining (recommended) if (posthog) { posthog.capture('test') // using an if statement } }, [posthog])Typescript helps protect against these errors.
Verify client-side events are captured
Checkpoint
Confirm that you can capture client-side events and see them in your PostHog project
At this point, you should be able to capture client-side events and see them in your PostHog project. This includes basic events like page views and button clicks that are autocaptured.
You can also try to capture a custom event to verify it's working. You can access PostHog in any component using the
usePostHoghook.import { usePostHog } from '@posthog/react' function App() { const posthog = usePostHog() return <button onClick={() => posthog?.capture('button_clicked')}>Click me</button> }You should see these events in a minute or two in the activity tab.
4
Access PostHog methods
Required
On the client-side, you can access the PostHog client using the
usePostHoghook. This hook returns the initialized PostHog client, which you can use to call PostHog methods. For example:import { usePostHog } from '@posthog/react' function App() { const posthog = usePostHog() return <button onClick={() => posthog?.capture('button_clicked')}>Click me</button> }For a complete list of available methods, see the posthog-js documentation.
5
Identify your user
Recommended
Now that you can capture basic client-side events, you'll want to identify your user so you can associate users with captured events.
Generally, you identify users when they log in or when they input some identifiable information (e.g. email, name, etc.). You can identify users by calling the
identifymethod on the PostHog client:export default function Login() { const { user, login } = useAuth(); const posthog = usePostHog(); const handleLogin = async (e: React.FormEvent) => { // existing code to handle login... const user = await login({ email, password }); posthog?.identify(user.email, { email: user.email, name: user.name, } ); posthog?.capture('user_logged_in'); }; return ( <div> {/* ... existing code ... */} <button onClick={handleLogin}>Login</button> </div> ); }PostHog automatically generates anonymous IDs for users before they're identified. When you call identify, a new identified person is created. All previous events tracked with the anonymous ID link to the new identified distinct ID, and all future captures on the same browser associate with the identified person.
6
Create an error boundary
Recommended
PostHog can capture exceptions thrown in your app through an error boundary. React Router in framework mode has a built-in error boundary that you can use to capture exceptions. You can create an error boundary by exporting
ErrorBoundaryfrom yourapp/root.tsxfile.app/root.tsx
import { usePostHog } from '@posthog/react' export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { const posthog = usePostHog(); posthog?.captureException(error); // other error handling code... return ( <div> <h1>Something went wrong</h1> <p>{error.message}</p> </div> ); }This automatically captures exceptions thrown in your React Router app using the
posthog.captureException()method.7
Tracking element visibility
Recommended
The
PostHogCaptureOnViewedcomponent enables you to automatically capture events when elements scroll into view in the browser. This is useful for tracking impressions of important content, monitoring user engagement with specific sections, or understanding which parts of your page users are actually seeing.The component wraps your content and sends a
$element_viewedevent to PostHog when the wrapped element becomes visible in the viewport. It only fires once per component instance.Basic usage:
import { PostHogCaptureOnViewed } from '@posthog/react' function App() { return ( <PostHogCaptureOnViewed name="hero-banner"> <div>Your important content here</div> </PostHogCaptureOnViewed> ) }With custom properties:
You can include additional properties with the event to provide more context:
<PostHogCaptureOnViewed name="product-card" properties={{ product_id: '123', category: 'electronics', price: 299.99 }} > <ProductCard /> </PostHogCaptureOnViewed>Tracking multiple children:
Use
trackAllChildrento track each child element separately. This is useful for galleries or lists where you want to know which specific items were viewed:<PostHogCaptureOnViewed name="product-gallery" properties={{ gallery_type: 'featured' }} trackAllChildren > <ProductCard id="1" /> <ProductCard id="2" /> <ProductCard id="3" /> </PostHogCaptureOnViewed>When
trackAllChildrenis enabled, each child element sends its own event with achild_indexproperty indicating its position.Custom intersection observer options:
You can customize when elements are considered "viewed" by passing options to the
IntersectionObserver:<PostHogCaptureOnViewed name="footer" observerOptions={{ threshold: 0.5, // Element is 50% visible rootMargin: '0px' }} > <Footer /> </PostHogCaptureOnViewed>The component passes all other props to the wrapper
div, so you can add styling, classes, or other HTML attributes as needed.8
Install server-side SDKs
Recommended
Install the PostHog Node SDK using your package manager. This is the SDK you'll use to capture server-side events.
npm
npm install posthog-node --saveYarn
yarn add posthog-nodepnpm
pnpm add posthog-nodeBun
bun add posthog-node9
Create a server-side middleware
Recommended
Use one PostHog client for a long-running Node server. Create it in a server-only module and import it from your middleware and tracing setup.
app/lib/posthog.server.ts
import { PostHog } from 'posthog-node'; export const posthog = new PostHog(process.env.VITE_POSTHOG_PROJECT_TOKEN!, { host: process.env.VITE_POSTHOG_HOST!, });In React Router 7, enable server middleware in
react-router.config.ts:react-router.config.ts
export default { future: { v8_middleware: true }, };React Router 8 enables server middleware by default. Add the following middleware. It reads PostHog tracing headers and keeps each request context separate. Missing or blank headers add no ID. Header IDs are analytics context and must not authorize access.
app/lib/posthog-middleware.server.ts
import { createContext, type MiddlewareFunction } from 'react-router'; import { posthog } from './posthog.server'; type RequestIds = { distinctId?: string; sessionId?: string }; export const posthogClientContext = createContext<typeof posthog>(); export const posthogErrorContext = createContext<RequestIds | undefined>(undefined); function tracingId(value: string | null): string | undefined { return value?.replace(/[\x00-\x1F\x7F-\x9F]/g, '').trim().slice(0, 1000) || undefined; } export const posthogMiddleware: MiddlewareFunction<Response> = async ({ request, context }, next) => { const existing = posthog.getContext(); const data: RequestIds = { distinctId: existing?.distinctId ?? tracingId(request.headers.get('x-posthog-distinct-id')), sessionId: existing?.sessionId ?? tracingId(request.headers.get('x-posthog-session-id')), }; context.set(posthogClientContext, posthog); context.set(posthogErrorContext, data); return posthog.withContext(data, () => next(), { fresh: true }); };Register the middleware in
app/root.tsx. Put an authentication middleware before it if you useposthog.withContextto establish a verified application identity. Existing application IDs take priority over header IDs.app/root.tsx
import { posthogMiddleware } from './lib/posthog-middleware.server'; export const middleware: Route.MiddlewareFunction[] = [ posthogMiddleware, // other middlewares... ];Work that the request starts keeps its IDs after
next()settles. Pass explicit context to separate jobs that need their own IDs. Flush queued events before your platform can stop server work. Callposthog.shutdown()once when the server stops.To capture server errors, add the capture call to your existing
handleErrorexport inapp/entry.server.tsx. The saved IDs remain available when a middleware error reaches this hook outside the scope. Errors before this middleware runs have no saved request identity.app/entry.server.tsx
import type { HandleErrorFunction } from 'react-router'; import { posthog } from './lib/posthog.server'; import { posthogErrorContext } from './lib/posthog-middleware.server'; export const handleError: HandleErrorFunction = (error, { context }) => { const saved = context.get(posthogErrorContext); posthog.captureException(error, saved?.distinctId, saved?.sessionId ? { $session_id: saved.sessionId } : undefined); };Optional server span context
Use the optional
posthog-node/otelentrypoint when your Node SDK includes it. Addnew PostHogContextSpanProcessor(posthog)to the processors in your existing OpenTelemetry setup. Import the processor fromposthog-node/oteland reuse the client fromposthog.server.ts. Keep your provider, exporters, and sampler.Import the helper in
posthog-middleware.server.ts:import { enrichCurrentSpan } from 'posthog-node/otel';Inside the
withContextcallback, call it immediately beforenext():enrichCurrentSpan(posthog); return await next();The helper fills missing
posthogDistinctIdandsessionIdattributes on the active span and its nearest recording server ancestor whose parent chain the processor observed. Explicit span attributes stay unchanged. The processor adds the request IDs to new child spans. If a sampler drops an intermediate span, the earlier server span stays unchanged because its parent chain cannot be verified.React Router provides server instrumentation hooks for application-owned tracing. Their stable export,
instrumentations, is available from 7.15.0. This setup does not create request spans. Configure those hooks or your existing server tracing to start a span before the middleware runs. This recipe was tested with React Router 7.15.0 and 8.4.0 on Node.Verify server-side events are captured
Checkpoint
Confirm that you can capture server-side events and see them in your PostHog project
At this point, you should be able to capture server-side events and see them in your PostHog project.
In a route, you can access the PostHog client from the context and capture an event. The middleware assigns the session ID and the distinct ID. This ensures that the system associates events with the correct user and session.
app/routes/api.checkout.ts
import { posthogClientContext } from "../lib/posthog-middleware.server"; export async function action({ request, context }: Route.ActionArgs) { const body = await request.json(); // ... existing code ... // Access the PostHog client from the context and capture an event const posthog = context.get(posthogClientContext); posthog?.capture({ event: 'checkout_completed' }); return Response.json({ success: true, // ... existing code ... }); }10
Next steps
Recommended
Now that you've set up PostHog for React Router, you can start capturing events and exceptions in your app.
To get the most out of PostHog, you should familiarize yourself with the following:
PostHog Web SDK docs: Learn more about the PostHog Web SDK and how to use it on the client-side.
PostHog Node SDK docs: Learn more about the PostHog Node SDK and how to use it on the server-side.
Identify users: Learn more about how to identify users in your app.
Group analytics: Learn more about how to use group analytics in your app.
PostHog AI: After capturing events, use PostHog AI to help you understand your data and build insights.
Feature flags and experiments: Feature flag and experiment setup is the same as React. You can find more details in the React integration guide.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server, Needs Python, Needs Node.js
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- PostHog
- Publisher
- PostHog (Official)
- Source
- PostHog/skills · skills/omnibus/instrument-integration · commit a5d30e7
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by PostHog or PostHog.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download instrument-integration.zip