# PostHog Next.js App Router Example Project Repository: https://github.com/PostHog/context-mill Path: basics/next-app-router --- ## README.md # PostHog Next.js app router example This is a [Next.js](https://nextjs.org) App Router example demonstrating PostHog integration with product analytics, session replay, feature flags, and error tracking. ## Features - **Product analytics**: Track user events and behaviors - **Session replay**: Record and replay user sessions - **Error tracking**: Capture and track errors - **User authentication**: Demo login system with PostHog user identification - **Server-side & Client-side tracking**: Examples of both tracking methods - **Reverse proxy**: PostHog ingestion through Next.js rewrites ## Getting started ### 1. Install dependencies ```bash npm install # or pnpm install ``` ### 2. Configure environment variables Create a `.env.local` file in the root directory: ```bash NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=your_posthog_project_token NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com ``` Get your PostHog project token from your [PostHog project settings](https://app.posthog.com/project/settings). ### 3. Run the development server ```bash npm run dev # or pnpm dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the app. ## Project structure ``` src/ ├── app/ │ ├── api/ │ │ └── auth/ │ │ └── login/ │ │ └── route.ts # Login API with server-side tracking │ ├── burrito/ │ │ └── page.tsx # Demo feature page with event tracking │ ├── profile/ │ │ └── page.tsx # User profile with error tracking demo │ ├── layout.tsx # Root layout with providers │ ├── page.tsx # Home/Login page │ └── globals.css # Global styles ├── components/ │ └── Header.tsx # Navigation header with auth state ├── contexts/ │ └── AuthContext.tsx # Authentication context with PostHog integration └── lib/ └── posthog-server.ts # Server-side PostHog client instrumentation-client.ts # Client-side PostHog initialization ``` ## Key integration points ### Client-side initialization (instrumentation-client.ts) ```typescript import posthog from "posthog-js"; posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, { api_host: "/ingest", ui_host: "https://us.posthog.com", defaults: "2026-01-30", capture_exceptions: true, debug: process.env.NODE_ENV === "development", }); ``` ### User identification (AuthContext.tsx) ```typescript posthog.identify(username, { username: username, }); ``` ### Event tracking (burrito/page.tsx) ```typescript posthog.capture("burrito_considered", { total_considerations: count, username: username, }); ``` ### Error tracking (profile/page.tsx) ```typescript posthog.captureException(error); ``` ### Server-side tracking (app/api/auth/login/route.ts) ```typescript const posthog = getPostHogClient(); posthog.capture({ distinctId: username, event: 'server_login', properties: { ... } }); ``` ## App router differences from pages router This example uses Next.js App Router instead of Pages Router. Key differences: 1. **File-based routing**: Pages in `src/app/` instead of `src/pages/` 2. **layout.tsx**: Root layout component wraps all pages 3. **API Routes**: Located in `src/app/api/` with `route.ts` files 4. **'use client'**: Client components need explicit directive 5. **useRouter**: From `next/navigation` instead of `next/router` 6. **Metadata**: Exported from layout/page instead of Head component 7. **Server Components**: Components are server-side by default ## Learn more - [PostHog Documentation](https://posthog.com/docs) - [Next.js App Router Documentation](https://nextjs.org/docs/app) - [PostHog Next.js Integration Guide](https://posthog.com/docs/libraries/next-js) ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new). Check out the [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. --- ## .env.example ```example # PostHog Configuration # Get your PostHog project token from: https://app.posthog.com/project/settings NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=your_posthog_project_token_here # NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com ``` --- ## instrumentation-client.ts ```ts import posthog from "posthog-js"; posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, { api_host: "/ingest", ui_host: "https://us.posthog.com", // Include the defaults option as required by PostHog defaults: "2026-01-30", // Enables capturing unhandled exceptions via Error Tracking capture_exceptions: true, // Turn on debug in development mode debug: process.env.NODE_ENV === "development", }); //IMPORTANT: Never combine this approach with other client-side PostHog initialization approaches, especially components like a PostHogProvider. instrumentation-client.ts is the correct solution for initializating client-side PostHog in Next.js 15.3+ apps. ``` --- ## next.config.ts ```ts import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ async rewrites() { return [ { source: "/ingest/static/:path*", destination: "https://us-assets.i.posthog.com/static/:path*", }, { source: "/ingest/:path*", destination: "https://us.i.posthog.com/:path*", }, ]; }, // This is required to support PostHog trailing slash API requests skipTrailingSlashRedirect: true, }; export default nextConfig; ``` --- ## src/app/api/auth/login/route.ts ```ts import { NextResponse } from "next/server"; import { getPostHogClient } from "@/lib/posthog-server"; const users = new Map< string, { username: string; burritoConsiderations: number } >(); export async function POST(request: Request) { const { username, password } = await request.json(); if (!username || !password) { return NextResponse.json( { error: "Username and password required" }, { status: 400 }, ); } let user = users.get(username); const isNewUser = !user; if (!user) { user = { username, burritoConsiderations: 0 }; users.set(username, user); } // Capture server-side login event const posthog = getPostHogClient(); posthog.capture({ distinctId: username, event: "server_login", properties: { username: username, isNewUser: isNewUser, source: "api", }, }); // Identify user on server side posthog.identify({ distinctId: username, properties: { username: username, createdAt: isNewUser ? new Date().toISOString() : undefined, }, }); return NextResponse.json({ success: true, user }); } ``` --- ## src/app/burrito/page.tsx ```tsx "use client"; import { useState } from "react"; import { useAuth } from "@/contexts/AuthContext"; import { useRouter } from "next/navigation"; import posthog from "posthog-js"; export default function BurritoPage() { const { user, incrementBurritoConsiderations } = useAuth(); const router = useRouter(); const [hasConsidered, setHasConsidered] = useState(false); // Redirect to home if not logged in if (!user) { router.push("/"); return null; } const handleConsideration = () => { incrementBurritoConsiderations(); setHasConsidered(true); setTimeout(() => setHasConsidered(false), 2000); // Capture burrito consideration event posthog.capture("burrito_considered", { total_considerations: user.burritoConsiderations + 1, username: user.username, }); }; return (
Take a moment to truly consider the potential of burritos.
Thank you for your consideration! Count:{" "} {user.burritoConsiderations}
)}Total considerations: {user.burritoConsiderations}
You are now logged in. Feel free to explore:
Please sign in to begin your burrito journey
Note: This is a demo app. Use any username and password to sign in.
Username: {user.username}
Burrito Considerations: {user.burritoConsiderations}
You haven't considered any burritos yet. Visit the Burrito Consideration page to start!
) : user.burritoConsiderations === 1 ? (You've considered the burrito potential once. Keep going!
) : user.burritoConsiderations < 5 ? (You're getting the hang of burrito consideration!
) : user.burritoConsiderations < 10 ? (You're becoming a burrito consideration expert!
) : (You are a true burrito consideration master! 🌯
)}