PostHog integration for Next.js App Router
This skill helps you add PostHog analytics to Next.js App Router applications.
Workflow
Follow these steps in order to complete the integration:
references/1-begin.md - PostHog Setup - Begin ← Start here
references/2-edit.md - PostHog Setup - Edit
references/3-revise.md - PostHog Setup - Revise
references/4-conclude.md - PostHog Setup - Conclusion
Reference files
references/EXAMPLE.md - Next.js App Router example project code
references/1-begin.md - Start the event tracking setup process by analyzing the project and creating an event tracking plan
references/2-edit.md - Implement PostHog event tracking in the identified files, following best practices and the example project
references/3-revise.md - Review and fix any errors in the PostHog integration implementation
references/4-conclude.md - Review and fix any errors in the PostHog integration implementation
references/next-js.md - Next.js - docs
references/identify-users.md - Identify users - docs
references/COMMANDMENTS.md - Framework-specific rules the integration must follow
The example project shows the target implementation pattern. Consult the documentation for API details.
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.
Framework guidelines
- For Next.js 15.3+, initialize PostHog in instrumentation-client.ts for the simplest setup
- For feature flags, use useFeatureFlagEnabled() or useFeatureFlagPayload() hooks - they handle loading states and external sync automatically
- Add analytics capture in event handlers where user actions occur, NOT in useEffect reacting to state changes
- Do NOT use useEffect for data transformation - calculate derived values during render instead
- Do NOT use useEffect to respond to user events - put that logic in the event handler itself
- Do NOT use useEffect to chain state updates - calculate all related updates together in the event handler
- Do NOT use useEffect to notify parent components - call the parent callback alongside setState in the event handler
- To reset component state when a prop changes, pass the prop as the component's key instead of using useEffect
- useEffect is ONLY for synchronizing with external systems (non-React widgets, browser APIs, network subscriptions)
- Remember that source code is available in the node_modules directory
- Check package.json for type checking or build scripts to validate changes
- When a reverse proxy is configured, both /static/* AND /array/* must route to the assets origin (us-assets.i.posthog.com or eu-assets.i.posthog.com).
- posthog-js is the JavaScript SDK package name
- posthog.init() MUST be called before any other PostHog methods (capture, identify, etc.)
- posthog-js is browser-only — do NOT import it in Node.js or server-side contexts (use posthog-node instead)
- Autocapture is ON by default with posthog-js (tracks clicks, form submissions, pageviews). Keep autocapture enabled unless the user explicitly asks to turn it off.
- NEVER send PII in posthog.capture() event properties — no emails, full names, phone numbers, physical addresses, IP addresses, or user-generated content
- PII belongs in posthog.identify() person properties (email, name, role), NOT in capture() event properties
- Call posthog.identify(userId, { email, name, role }) on login AND on page refresh if the user is already logged in
- Call posthog.reset() on logout to unlink future events from the current user
- For SPAs without a framework router, capture pageviews with posthog.capture($pageview) or use the capture_pageview history_change option in init for History API routing
- posthog-node is the Node.js server-side SDK package name; posthog-js is browser-only, so use posthog-node on the server instead
- Include enableExceptionAutocapture: true in the PostHog constructor options
- Add posthog.capture() calls in route handlers for meaningful user actions – every route that creates, updates, or deletes data should track an event with contextual properties
- Add posthog.captureException(err, distinctId) in the application's error handler (e.g., Express error middleware, Fastify setErrorHandler, Koa app.on('error'))
- The SDK batches events and flushes asynchronously. await flush() or await shutdown() before letting that process exit. If unsure, set flushAt 1 and flushInterval 0.
posthog.capture() enqueues synchronously and returns; the batched HTTP send happens afterwards. Treat every per-request handler as short-lived even when the framework feels like a server: Next.js / Nuxt / SvelteKit / Remix route handlers, serverless and edge functions, and Lambda are torn down per invocation before the send runs. Create the client with flushAt 1 and flushInterval 0, then await the send before returning. Always use await posthog.flush() for a shared/singleton client, await posthog.shutdown() for a per-request client. Never skip the awaited flush or risk the enqueued event being silently dropped.
- Reverse proxy is NOT needed for server-side Node.js – only client-side JavaScript needs a proxy to avoid ad blockers
Identifying users
Identify users during login and signup events. Refer to the example code and documentation for the correct identify pattern for this framework. If both frontend and backend code exist, pass the client-side session and distinct ID using X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID headers to maintain correlation.
Error tracking
Add PostHog error tracking to relevant files, particularly around critical user flows and API boundaries.
1---2name: integration-nextjs-app-router3description: PostHog integration for Next.js App Router applications4---56# PostHog integration for Next.js App Router78This skill helps you add PostHog analytics to Next.js App Router applications.910## Workflow1112Follow these steps in order to complete the integration:13141. `references/1-begin.md` - PostHog Setup - Begin ← **Start here**152. `references/2-edit.md` - PostHog Setup - Edit163. `references/3-revise.md` - PostHog Setup - Revise174. `references/4-conclude.md` - PostHog Setup - Conclusion1819## Reference files2021- `references/EXAMPLE.md` - Next.js App Router example project code22- `references/1-begin.md` - Start the event tracking setup process by analyzing the project and creating an event tracking plan23- `references/2-edit.md` - Implement PostHog event tracking in the identified files, following best practices and the example project24- `references/3-revise.md` - Review and fix any errors in the PostHog integration implementation25- `references/4-conclude.md` - Review and fix any errors in the PostHog integration implementation26- `references/next-js.md` - Next.js - docs27- `references/identify-users.md` - Identify users - docs28- `references/COMMANDMENTS.md` - Framework-specific rules the integration must follow2930The example project shows the target implementation pattern. Consult the documentation for API details.3132## Key principles3334- **Environment variables**: Always use environment variables for PostHog keys. Never hardcode them.35- **Minimal changes**: Add PostHog code alongside existing integrations. Don't replace or restructure existing code.36- **Match the example**: Your implementation should follow the example project's patterns as closely as possible.3738## Framework guidelines3940- For Next.js 15.3+, initialize PostHog in instrumentation-client.ts for the simplest setup41- For feature flags, use useFeatureFlagEnabled() or useFeatureFlagPayload() hooks - they handle loading states and external sync automatically42- Add analytics capture in event handlers where user actions occur, NOT in useEffect reacting to state changes43- Do NOT use useEffect for data transformation - calculate derived values during render instead44- Do NOT use useEffect to respond to user events - put that logic in the event handler itself45- Do NOT use useEffect to chain state updates - calculate all related updates together in the event handler46- Do NOT use useEffect to notify parent components - call the parent callback alongside setState in the event handler47- To reset component state when a prop changes, pass the prop as the component's key instead of using useEffect48- useEffect is ONLY for synchronizing with external systems (non-React widgets, browser APIs, network subscriptions)49- Remember that source code is available in the node_modules directory50- Check package.json for type checking or build scripts to validate changes51- When a reverse proxy is configured, both /static/* AND /array/* must route to the assets origin (us-assets.i.posthog.com or eu-assets.i.posthog.com).52- posthog-js is the JavaScript SDK package name53- posthog.init() MUST be called before any other PostHog methods (capture, identify, etc.)54- posthog-js is browser-only — do NOT import it in Node.js or server-side contexts (use posthog-node instead)55- Autocapture is ON by default with posthog-js (tracks clicks, form submissions, pageviews). Keep autocapture enabled unless the user explicitly asks to turn it off.56- NEVER send PII in posthog.capture() event properties — no emails, full names, phone numbers, physical addresses, IP addresses, or user-generated content57- PII belongs in posthog.identify() person properties (email, name, role), NOT in capture() event properties58- Call posthog.identify(userId, { email, name, role }) on login AND on page refresh if the user is already logged in59- Call posthog.reset() on logout to unlink future events from the current user60- For SPAs without a framework router, capture pageviews with posthog.capture($pageview) or use the capture_pageview history_change option in init for History API routing61- posthog-node is the Node.js server-side SDK package name; posthog-js is browser-only, so use posthog-node on the server instead62- Include enableExceptionAutocapture: true in the PostHog constructor options63- Add posthog.capture() calls in route handlers for meaningful user actions – every route that creates, updates, or deletes data should track an event with contextual properties64- Add posthog.captureException(err, distinctId) in the application's error handler (e.g., Express error middleware, Fastify setErrorHandler, Koa app.on('error'))65- The SDK batches events and flushes asynchronously. await flush() or await shutdown() before letting that process exit. If unsure, set flushAt 1 and flushInterval 0.66- `posthog.capture()` enqueues synchronously and returns; the batched HTTP send happens afterwards. Treat every per-request handler as short-lived even when the framework feels like a server: Next.js / Nuxt / SvelteKit / Remix route handlers, serverless and edge functions, and Lambda are torn down per invocation before the send runs. Create the client with flushAt 1 and flushInterval 0, then await the send before returning. Always use `await posthog.flush()` for a shared/singleton client, `await posthog.shutdown()` for a per-request client. Never skip the awaited flush or risk the enqueued event being silently dropped.67- Reverse proxy is NOT needed for server-side Node.js – only client-side JavaScript needs a proxy to avoid ad blockers6869## Identifying users7071Identify users during login and signup events. Refer to the example code and documentation for the correct identify pattern for this framework. If both frontend and backend code exist, pass the client-side session and distinct ID using `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` headers to maintain correlation.7273## Error tracking7475Add PostHog error tracking to relevant files, particularly around critical user flows and API boundaries.