User Identification in Next.js with Clerk and PostHog
Guide to setting up Clerk & PostHog on Next.js with a copy-paste code snippet.
I have recently been really enjoying using Clerk when building my side projects to get authentication completed in an easy but scalable way. Authentication isn't a differentiator for my projects, so an off-the-shelf solution is the best option for starting quickly and scaling. I have also been using PostHog as an all-in-one solution for metrics, event, error tracking, and feature flags. To get it all integrated together, I combined a few examples into a provider component that tracks user identification using Clerk. The code below is a dropping component to enable all the functionality required for tracking a simple project.
Adding User Identification
- Add the
PostHogProvider.tsx
component to your project.
"use client";
import { useAuth, useUser } from "@clerk/nextjs";
import { usePathname, useSearchParams } from "next/navigation";
import posthog from "posthog-js";
import { PostHogProvider as PHProvider, usePostHog } from "posthog-js/react";
import { Suspense, useEffect } from "react";
export function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: "/ingest",
ui_host: "https://us.posthog.com",
capture_pageview: false, // We capture pageviews manually
capture_pageleave: true, // Enable pageleave capture
capture_exceptions: true, // This enables capturing exceptions using Error Tracking, set to false if you don't want this
debug: process.env.NODE_ENV === "development",
});
}, []);
return <PHProvider client={posthog}>{children}</PHProvider>;
}
function PostHogPageView() {
const pathname = usePathname();
const searchParams = useSearchParams();
const posthogClient = usePostHog();
const { isSignedIn, userId } = useAuth();
const { user } = useUser();
// Track pageviews
useEffect(() => {
if (pathname && posthogClient) {
let url = window.origin + pathname;
const search = searchParams.toString();
if (search) {
url += "?" + search;
}
posthogClient.capture("$pageview", { $current_url: url });
}
}, [pathname, searchParams, posthogClient]);
// Handle user authentication and identification
useEffect(() => {
// Check the sign in status and user info,
// and identify the user if they aren't already
if (isSignedIn && userId && user && !posthogClient._isIdentified()) {
// Identify the user
posthogClient.identify(userId, {
email: user.primaryEmailAddress?.emailAddress,
username: user.username,
name: user.fullName,
});
}
// Reset the user if they sign out
if (!isSignedIn && posthogClient._isIdentified()) {
posthogClient.reset();
}
}, [posthogClient, isSignedIn, userId, user]);
return null;
}
export function SuspendedPostHogPageView() {
return (
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
);
}
PostHogProvider.tsx
- Wrap
PostHogProvider
around your layout file and addSuspendedPostHogPageView
inside thePostHogProvider
&Clerk
Provider
<PostHogProvider>
<ClerkProvider>
{children}
<SuspendedPostHogPageView />
</ClerkProvider>
</PostHogProvider>
layout.tsx
📖
The post was proofread by Improve My Writing. Start improving your writing for free today.