How to set up Next.js app router analytics, feature flags, and more
Jul 15, 2025
Next.js is one of the most popular frameworks for building web apps. Have one and need to know what users are doing in these apps or release a feature safely? PostHog can help.
In this tutorial, we'll create a simple Next.js app and set up PostHog on the client and server side. We'll also capture pageviews and custom events, set up feature flags, and more.
If you use Next.js with the **pages** router, check out our other [Next.js pages router analytics tutorial](/tutorials/nextjs-analytics).
Creating a Next.js app with the app router
First, once Node is installed, create a Next.js app. Select No for TypeScript, Yes for use app router
, and the defaults for every other option.
npx create-next-app@latest next-app
We name our app next-app
and can go into the newly created folder to run it.
cd next-appnpm run dev
This opens a new page showing we are running Next.js.
Next, we'll add a couple pages to the app. In the app folder, create a new folder named about
and create a page.js
file inside with a basic component.
// app/about/page.jsimport Link from 'next/link'export default function About() {return (<main><h1>About</h1><Link href="/">Go home</Link></main>)}
Change our app page.js
file to a title and a link to the new about
page.
// app/page.jsimport styles from './page.module.css'import Link from 'next/link'export default function Home() {return (<main className={styles.main}><h1>Home</h1><Link href="/about">Go to About</Link></main>)}
You can now move between the home and about pages which will be useful for testing event capture next.
Setting up PostHog on the client side
First, sign up for PostHog if you haven't already (it's free).
Next, we need a project API key and client API host. You can get both from your project settings. Add both of these to a .env.local
file in our base directory.
NEXT_PUBLIC_POSTHOG_KEY=<ph_project_api_key>NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
We can now initialize PostHog. Next.js 15.3 added support for instrumentation-client.js|ts
which we can use to initialize PostHog. Create a instrumentation-client.js
file in the base directory and add the following code:
// instrumentation-client.jsimport posthog from 'posthog-js'posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,defaults: '2025-05-24',});
After you set this up and restart your app, PostHog starts autocapturing events and pageviews. If you have enabled session replays in your project settings, these will also be captured.


We highly recommend setting up a reverse proxy to ensure requests aren't blocked by tracking blockers. You can find instructions for doing this in our reverse proxy guide.
Capturing custom events
Initializing PostHog on the client side means you can use it in all your client-side rendered Next.js components (the ones with the "use client"
directive). To show this, we can create a button that captures a custom event when clicked.
Simply, import PostHog into your page.js
and call posthog.capture()
when the button is clicked like this:
// app/page.js'use client'import styles from './page.module.css'import Link from 'next/link'import posthog from 'posthog-js'export default function Home() {const captureButtonClick = () => {posthog.capture('button_clicked', {cool: true})}return (<main className={styles.main}><h1>Home</h1><Link href="/about">Go to About</Link><button onClick={captureButtonClick}>Click me</button></main>)}
Setting up feature flags
You can use feature flags to control what users see in your app. For example, you can show a new feature to a subset of users and then roll it out to all users over time.
To set one up, go to the feature flags page in PostHog and create a new feature flag. Give it a key (I chose main-cta
), make sure to roll it out to 100% of users, and click Save.


Next, we can add an isFeatureEnabled
check to our page.js
file to show a link to PostHog if the feature flag is enabled.
// app/page.js'use client'import styles from './page.module.css'import Link from 'next/link'import posthog from 'posthog-js'import { useState, useEffect } from 'react'export default function Home() {const [showMainCTA, setShowMainCTA] = useState(false)const captureButtonClick = () => {posthog.capture('button_clicked', {cool: true})}useEffect(() => {setShowMainCTA(posthog.isFeatureEnabled('main-cta'))}, [])return (<main className={styles.main}><h1>Home</h1><Link href="/about">Go to About</Link><button onClick={captureButtonClick}>Click me</button>{showMainCTA && <button>Sign up to get rich!!!</button>}</main>)}
When you go to the home page, you should see the new button. You can also test that disabling the feature flag removes the button.
Setting up PostHog on the server side
A key part of the popularity of Next.js its is combination of client and server options. PostHog supports both, but you need to install and use the PostHog Node SDK for the server side.
To do this, start by installing the PostHog Node SDK.
npm install posthog-node
Next, create a posthog.js
file in the app folder that returns a PostHog Node client:
// app/posthog.jsimport { PostHog } from 'posthog-node'export default function PostHogClient() {const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {host: process.env.NEXT_PUBLIC_POSTHOG_HOST,flushAt: 1,flushInterval: 0})posthogClient.debug(true)return posthogClient}
This can then be used in API routes and server-rendered components.
Using PostHog in API routes
To show you how to add PostHog to Next.js API routes do this, we can create a simple API route that captures a custom event.
Start by creating a new api
folder in the app
folder, a hello
folder inside it, and then a route.js
file inside that. In it, import the PostHogClient
from the posthog.js
file, set up some code to get the distinct_id
from the request cookies, and call posthog.capture()
like this:
// app/api/hello/route.jsimport posthogClient from '../../posthog'export async function GET(request) {const cookieName = 'ph_' + process.env.NEXT_PUBLIC_POSTHOG_KEY + '_posthog'const cookieValue = request.cookies.get(cookieName)?.valueconst distinctId = cookieValue ? JSON.parse(cookieValue).distinct_id : 'placeholder'const posthog = posthogClient()posthog.capture({distinctId: distinctId,event: 'hello_world',properties: {message: "Howdy!"}})return Response.json({ message: "Howdy!" });}
Now when you go (or make a request) to http://localhost:3000/api/hello
, you should see the event captured in PostHog.
Note: The
distinct_id
is a unique identifier for a user. We use the one from the cookie with a placeholder for now, but recommend using one from your database or authentication system.
Setting up feature flags in API routes
Feature flags are similar for API routes. Just call posthog.isFeatureEnabled()
with the flag key and a distinct ID like this:
// app/api/hello/route.jsimport posthogClient from '../../posthog'export async function GET(request) {const cookieName = 'ph_' + process.env.NEXT_PUBLIC_POSTHOG_KEY + '_posthog'const cookieValue = request.cookies.get(cookieName)?.valueconst distinctId = cookieValue ? JSON.parse(cookieValue).distinct_id : 'placeholder'const posthog = posthogClient()const isMainCTAEnabled = await posthog.isFeatureEnabled('main-cta', distinctId)console.log(isMainCTAEnabled)return Response.json({ message: "Howdy!" });}
With this, you have the basics of PostHog set up on both the client and server side with Next.js and the app router.
Further reading
- How to set up Next.js analytics, feature flags, and more
- How to set up Next.js A/B tests
- An introductory guide to identifying users in PostHog
Subscribe to our newsletter
Product for Engineers
Read by 60,000+ founders and builders
We'll share your email with Substack
Questions? Ask Max AI.
It's easier than reading through 682 pages of documentation