Add PostHog analytics with heatmap support
All checks were successful
ci/woodpecker/push/deploy Pipeline was successful

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 16:49:52 +02:00
parent 0bf5a8a546
commit 2dadc5362d
10 changed files with 522 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { PostHogProvider } from "@/lib/posthog";
import "./globals.css";
const geistSans = Geist({
@@ -27,7 +28,7 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<PostHogProvider>{children}</PostHogProvider>
</body>
</html>
);

View File

@@ -1,3 +1,5 @@
import { PostHogTestButton } from "./posthog-test-button";
export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center">
@@ -13,6 +15,7 @@ export default function Home() {
<span>+</span>
<span>.NET 9</span>
</div>
<PostHogTestButton />
</main>
</div>
);

View File

@@ -0,0 +1,18 @@
"use client";
import { usePostHog } from "posthog-js/react";
export function PostHogTestButton() {
const posthog = usePostHog();
return (
<button
onClick={() =>
posthog.capture("my_custom_event", { property: "value" })
}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
>
Send PostHog Test Event
</button>
);
}

View File

@@ -0,0 +1,52 @@
"use client";
import posthog from "posthog-js";
import { PostHogProvider as PHProvider, usePostHog } from "posthog-js/react";
import { useEffect, Suspense } from "react";
import { usePathname, useSearchParams } from "next/navigation";
const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY;
const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://eu.i.posthog.com";
function PostHogPageView() {
const pathname = usePathname();
const searchParams = useSearchParams();
const ph = usePostHog();
useEffect(() => {
if (pathname && ph) {
let url = window.origin + pathname;
if (searchParams.toString()) {
url += "?" + searchParams.toString();
}
ph.capture("$pageview", { $current_url: url });
}
}, [pathname, searchParams, ph]);
return null;
}
export function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (POSTHOG_KEY) {
posthog.init(POSTHOG_KEY, {
api_host: POSTHOG_HOST,
capture_pageview: false, // manual pageview tracking for SPA
capture_pageleave: true,
});
}
}, []);
if (!POSTHOG_KEY) {
return <>{children}</>;
}
return (
<PHProvider client={posthog}>
<Suspense fallback={null}>
<PostHogPageView />
</Suspense>
{children}
</PHProvider>
);
}