PostHog makes it easy to get data about traffic and usage of your Next.js app. Integrating PostHog into your site enables analytics about user behavior, custom events capture, session recordings, feature flags, and more.
This guide walks you through integrating PostHog into your Next.js app using the React and the Node.js SDKs.
You can see a working example of this integration in our Next.js demo app
Next.js has both client and server-side rendering, as well as pages and app routers. We'll cover all of these options in this guide.
Prerequisites
To follow this guide along, you need:
- A PostHog instance (either Cloud or self-hosted)
- A Next.js application
Client-side setup
Install posthog-js
using your package manager:
yarn add posthog-js# ornpm install --save posthog-js
Add your environment variables to your .env.local
file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project API key in your project settings.
NEXT_PUBLIC_POSTHOG_KEY=<ph_project_api_key>NEXT_PUBLIC_POSTHOG_HOST=<ph_instance_address>
These values need to start with NEXT_PUBLIC_
to be accessible on the client-side.
Pages router
If your Next.js app uses the pages router, you can integrate PostHog at the root of your app (pages/_app.js
).
// pages/_app.jsimport { useEffect } from 'react'import { useRouter } from 'next/router'import posthog from 'posthog-js'import { PostHogProvider } from 'posthog-js/react'// Check that PostHog is client-side (used to handle Next.js SSR)if (typeof window !== 'undefined') {posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',// Enable debug mode in developmentloaded: (posthog) => {if (process.env.NODE_ENV === 'development') posthog.debug()}})}export default function App({ Component, pageProps }) {const router = useRouter()useEffect(() => {// Track page viewsconst handleRouteChange = () => posthog?.capture('$pageview')router.events.on('routeChangeComplete', handleRouteChange)return () => {router.events.off('routeChangeComplete', handleRouteChange)}}, [])return (<PostHogProvider client={posthog}><Component {...pageProps} /></PostHogProvider>)}
App router
If your Next.js app to uses the app router, you can integrate PostHog by creating a providers.js
file in your app folder. This is because the posthog-js
library needs to be initialized on the client-side using the Next.js 'use client'
directive.
// app/providers.js'use client'import posthog from 'posthog-js'import { PostHogProvider } from 'posthog-js/react'import { usePathname, useSearchParams } from "next/navigation";import { useEffect } from "react";if (typeof window !== 'undefined') {posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST})}export default function PHProvider({ children }) {const pathname = usePathname();const searchParams = useSearchParams();// Track pageviewsuseEffect(() => {if (pathname) {let url = window.origin + pathnameif (searchParams.toString()) {url = url + `?${searchParams.toString()}`}posthog.capture('$pageview',{'$current_url': url,})}}, [pathname, searchParams])return <PostHogProvider client={posthog}>{children}</PostHogProvider>}
Once created, you can import the providers.js
file into your app/layout.js
file, then wrap your app in the provider component.
// app/layout.jsimport './globals.css'import Providers from './providers'export default function RootLayout({ children }) {return (<html lang="en"><Providers><body>{children}</body></Providers></html>)}
Files and components accessing PostHog on the client-side need the 'use client'
directive.
Accessing PostHog using the provider
PostHog can then be accessed throughout your Next.js app by using the usePostHog
hook. See the React SDK docs for examples of how to use:
- posthog-js functions like custom event capture, user identification, and more.
- Feature flags including variants and payloads.
You can also read the full posthog-js documentation for all the usable functions.
Server-side analytics
Server-side rendering enables you to render pages on the server instead of the client. This can be useful for SEO, performance, and user experience.
To integrate PostHog into your Next.js app on the server-side you should use the Node SDK.
First, install the posthog-node
library:
yarn add posthog-node# ornpm install --save posthog-node
Pages router
For the pages router, we can use the getServerSideProps
function to access PostHog on the server-side, send events, evaluate feature flags, and more.
This looks like this:
// pages/posts/[id].jsimport { useContext, useEffect, useState } from 'react'import { getServerSession } from "next-auth/next"import { PostHog } from 'posthog-node'export default function Post({ post, flags }) {const [ctaState, setCtaState] = useState()useEffect(() => {if (flags) {setCtaState(flags['blog-cta'])}})return (<div><h1>{post.title}</h1><p>By: {post.author}</p><p>{post.content}</p>{ctaState &&<p><a href="/">Go to PostHog</a></p>}<button onClick={likePost}>Like</button></div>)}export async function getServerSideProps(ctx) {const session = await getServerSession(ctx.req, ctx.res)let flags = nullif (session) {const client = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY,{host: process.env.NEXT_PUBLIC_POSTHOG_HOST,})flags = await client.getAllFlags(session.user.email);client.capture(session.user.email, 'loaded blog article', { url: ctx.req.url })await client.shutdownAsync()}const { posts } = await import('../../blog.json')const post = posts.find((post) => post.id.toString() === ctx.params.id)return {props: {post,flags},}}
Note: Make sure to always call
client.shutdownAsync()
after sending events from the server-side. PostHog queues events into larger batches, and this call forces all batched events to be flushed immediately.
App router
For the app router, we can initialize the posthog-node
SDK in every component we need it, or we can create a PostHogClient
component to import into files like this:
// 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,})return posthogClient}
With this component, we can both send events in the body of a component, and get data from PostHog (such as feature flag evaluations) using the Next.js getData()
function.
import Link from 'next/link'import PostHogClient from '../posthog'export default async function About() {const flags = await getData();posthog.capture({distinctId: 'user_distinct_id', // replace with a user's distinct IDevent: 'server-side event'})return (<main><h1>About</h1><Link href="/">Go home</Link>{ flags['main-cta'] &&<Link href="http://posthog.com/">Go to PostHog</Link>}</main>)}async function getData() {const posthog = PostHogClient()const flags = await posthog.getAllFlags('user_distinct_id' // replace with a user's distinct ID);return flags}
Configuring a reverse proxy to PostHog
To improve the reliability of client-side tracking and make it less likely to be intercepted by tracking blockers, you can setup a reverse proxy in Next.js. See deploying a reverse proxy using Next.js rewrites or using Vercel writes.