
PostHog is a powerful open-source analytics platform that enables product teams to track user behavior, capture events, and gain actionable insights. Integrating PostHog with a Next.js application allows you to track authenticated users, custom events, and pageviews—even in apps with client-side navigation.
This tutorial will walk you through a production-grade integration of PostHog in a Next.js app, including:
Initializing PostHog only in production
Using the PostHog React provider
Identifying authenticated users
Manually capturing pageviews
Rewrite proxy to afford adblockers
First, install the required packages:
npm install posthog-js @posthog/reactOr with pnpm:
pnpm add posthog-js @posthog/reactCreate a provider component to initialize PostHog and wrap your app.
This ensures PostHog is only initialized in production and provides the PostHog context to your app.
// src/app/_providers/posthogProvider.tsx
"use client";
import { useEffect, Suspense } from "react";
import { PostHogProvider as PHProvider, usePostHog } from "posthog-js/react";
import posthog from "posthog-js";
import { useSession } from "next-auth/react"; // use your own provider
import { usePathname, useSearchParams } from "next/navigation";
export function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (process.env.NODE_ENV !== "development") {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY as string, {
api_host: `${process.env.NEXT_PUBLIC_BASE_URL}/ingest`,
ui_host: "https://us.posthog.com",
capture_pageview: false, // We'll capture manually
});
}
}, []);
if (process.env.NODE_ENV === "development") {
return <>{children}</>;
}
return (
<PHProvider client={posthog}>
<SuspendedPostHogPageView />
{children}
</PHProvider>
);
}
To associate analytics data with authenticated users, use NextAuth’s session data and PostHog’s identify method.
function PostHogPageView() {
const posthog = usePostHog();
const { data: userInfo } = useSession(); // get this from your auth provider
useEffect(() => {
if (userInfo?.user.id) {
posthog.identify(userInfo.user.id, {
email: userInfo.user.email,
});
} else {
posthog.reset();
}
}, [posthog, userInfo?.user]);
// ...
}
Next.js uses client-side navigation, so you need to manually capture pageviews on route changes.
import { usePathname, useSearchParams } from "next/navigation";
function PostHogPageView() {
// ... user identification code above
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname;
if (searchParams.toString()) {
url += "?" + searchParams.toString();
}
posthog.capture("$pageview", { $current_url: url });
}
}, [pathname, searchParams, posthog]);
return null;
}
Next.js recommends using Suspense to avoid de-optimizing your app into client-side rendering when using certain hooks.
function SuspendedPostHogPageView() {
return (
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
);
}
In your root layout wrap your app with the PostHogProvider:
// src/app/layout.tsx
import { PostHogProvider } from "./_providers/posthogProvider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return <PostHogProvider>{children}</PostHogProvider>;
}
Add your PostHog project key and base URL to your environment variables:
NEXT_PUBLIC_POSTHOG_KEY=phc_xxx_your_key
NEXT_PUBLIC_BASE_URL=https://yourdomain.com
To avoid CORS issues and ad blockers and keep your analytics endpoint stable, proxy PostHog requests through your Next.js app.
In your next.config.mjs:
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*",
},
];
},With this setup, you have a robust, production-ready PostHog integration in your Next.js app:
Analytics only runs in production
Authenticated users are identified
Pageviews are tracked accurately
SSR/CSR performance is preserved
You can now leverage PostHog’s full power for product analytics, user behavior tracking, and event capture in your Next.js application.
This is a minimal setup; you can be extended for more metrics.
Note: This article was scaffolded using AI referencing my project's codebase.
2
13
1