Skip to content

Commit 7c0b7c2

Browse files
authored
Merge pull request #361 from joshsny/implement-posthog
feat: add posthog integration for analytics
2 parents ae5c53c + 841b638 commit 7c0b7c2

File tree

7 files changed

+128
-2
lines changed

7 files changed

+128
-2
lines changed

.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
1212
# Disable Sentry warning with TurboPack
1313
SENTRY_SUPPRESS_TURBOPACK_WARNING=1
1414

15+
# PostHog
16+
NEXT_PUBLIC_POSTHOG_KEY=
17+
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
18+
1519
######## [BEGIN] SENSITIVE DATA ######## For security reason, don't update the following variables (secret key) directly in this file.
1620
######## Please create a new file named `.env.local`, all environment files ending with `.local` won't be tracked by Git.
1721
######## After creating the file, you can add the following variables.

.env.production

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
# Sentry DSN
1111
NEXT_PUBLIC_SENTRY_DSN=
1212

13+
# PostHog
14+
NEXT_PUBLIC_POSTHOG_KEY=
15+
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
16+
1317
######## [BEGIN] SENSITIVE DATA ######## For security reason, don't update the following variables (secret key) directly in this file.
1418
######## Please create a new file named `.env.production.local`, all environment files ending with `.local` won't be tracked by Git.
1519
######## After creating the file, you can add the following variables.

package-lock.json

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"pg": "^8.13.3",
4646
"pino": "^9.6.0",
4747
"pino-pretty": "^13.0.0",
48+
"posthog-js": "^1.223.3",
4849
"react": "19.0.0",
4950
"react-dom": "19.0.0",
5051
"react-hook-form": "^7.54.2",

src/app/[locale]/layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Metadata } from 'next';
22
import { DemoBadge } from '@/components/DemoBadge';
3+
import { PostHogProvider } from '@/components/providers/PostHogProvider';
34
import arcjet, { detectBot, request } from '@/libs/Arcjet';
45
import { Env } from '@/libs/Env';
56
import { routing } from '@/libs/i18nNavigation';
@@ -92,8 +93,9 @@ export default async function RootLayout(props: {
9293
locale={locale}
9394
messages={messages}
9495
>
95-
{props.children}
96-
96+
<PostHogProvider>
97+
{props.children}
98+
</PostHogProvider>
9799
<DemoBadge />
98100
</NextIntlClientProvider>
99101
</body>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use client';
2+
3+
import { Env } from '@/libs/Env';
4+
import { usePathname, useSearchParams } from 'next/navigation';
5+
import posthog from 'posthog-js';
6+
import { PostHogProvider as PHProvider, usePostHog } from 'posthog-js/react';
7+
import { Suspense, useEffect } from 'react';
8+
9+
export function PostHogProvider({ children }: { children: React.ReactNode }) {
10+
useEffect(() => {
11+
if (Env.NEXT_PUBLIC_POSTHOG_KEY) {
12+
posthog.init(Env.NEXT_PUBLIC_POSTHOG_KEY, {
13+
api_host: Env.NEXT_PUBLIC_POSTHOG_HOST,
14+
capture_pageview: false, // Disable automatic pageview capture, as we capture manually
15+
});
16+
} else {
17+
console.warn('PostHog not initialized, set a NEXT_PUBLIC_POSTHOG_KEY in your .env.local file to enable it');
18+
}
19+
}, []);
20+
21+
if (!Env.NEXT_PUBLIC_POSTHOG_KEY) {
22+
return children;
23+
}
24+
25+
return (
26+
<PHProvider client={posthog}>
27+
<SuspendedPostHogPageView />
28+
{children}
29+
</PHProvider>
30+
);
31+
}
32+
33+
function PostHogPageView(): null {
34+
const pathname = usePathname();
35+
const searchParams = useSearchParams();
36+
const posthog = usePostHog();
37+
38+
// Track pageviews
39+
useEffect(() => {
40+
if (pathname && posthog) {
41+
let url = window.origin + pathname;
42+
if (searchParams.toString()) {
43+
url = `${url}?${searchParams.toString()}`;
44+
}
45+
46+
posthog.capture('$pageview', { $current_url: url });
47+
}
48+
}, [pathname, searchParams, posthog]);
49+
50+
return null;
51+
}
52+
53+
// Wrap this in Suspense to avoid the `useSearchParams` usage above
54+
// from de-opting the whole app into client-side rendering
55+
// See: https://nextjs.org/docs/messages/deopted-into-client-rendering
56+
function SuspendedPostHogPageView() {
57+
return (
58+
<Suspense fallback={null}>
59+
<PostHogPageView />
60+
</Suspense>
61+
);
62+
}

src/libs/Env.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const Env = createEnv({
1212
NEXT_PUBLIC_APP_URL: z.string().optional(),
1313
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
1414
NEXT_PUBLIC_CLERK_SIGN_IN_URL: z.string().min(1),
15+
NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(),
16+
NEXT_PUBLIC_POSTHOG_HOST: z.string().optional(),
1517
},
1618
shared: {
1719
NODE_ENV: z.enum(['test', 'development', 'production']).optional(),
@@ -27,5 +29,7 @@ export const Env = createEnv({
2729
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
2830
NEXT_PUBLIC_CLERK_SIGN_IN_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL,
2931
NODE_ENV: process.env.NODE_ENV,
32+
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
33+
NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
3034
},
3135
});

0 commit comments

Comments
 (0)