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

  1. 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

  1. Wrap PostHogProvider around your layout file and add SuspendedPostHogPageView inside the PostHogProvider & 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.

Subscribe to maks112v

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe