feat: integrate PostHog for analytics tracking and add PostHogProvider component

This commit is contained in:
2025-06-20 03:07:06 +00:00
committed by GitHub
parent 6d6b1baa62
commit e4b239e230
8 changed files with 152 additions and 14 deletions

View File

@@ -2,7 +2,8 @@
NEXT_PUBLIC_NAVIDROME_URL=http://localhost:4533
NEXT_PUBLIC_NAVIDROME_USERNAME=your_username
NEXT_PUBLIC_NAVIDROME_PASSWORD=your_password
NEXT_PUBLIC_POSTHOG_KEY=KEY
NEXT_PUBLIC_POSTHOG_HOST=HOSTURL
# Example for external server:
# NEXT_PUBLIC_NAVIDROME_URL=https://your-navidrome-server.com
# NEXT_PUBLIC_NAVIDROME_USERNAME=your_username

View File

@@ -10,9 +10,10 @@ import { Progress } from '@/components/ui/progress';
import { useToast } from '@/hooks/use-toast';
export const AudioPlayer: React.FC = () => {
const { currentTrack, playPreviousTrack, addToQueue, playNextTrack, clearQueue } = useAudioPlayer();
const { currentTrack, playPreviousTrack, addToQueue, playNextTrack, clearQueue, queue } = useAudioPlayer();
const router = useRouter();
const audioRef = useRef<HTMLAudioElement>(null);
const preloadAudioRef = useRef<HTMLAudioElement>(null);
const [progress, setProgress] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [showVolumeSlider, setShowVolumeSlider] = useState(false);
@@ -324,6 +325,7 @@ export const AudioPlayer: React.FC = () => {
</div>
</div>
<audio ref={audioRef} hidden />
<audio ref={preloadAudioRef} hidden preload="metadata" />
</div>
);
}
@@ -377,6 +379,7 @@ export const AudioPlayer: React.FC = () => {
</div>
</div>
<audio ref={audioRef} hidden />
<audio ref={preloadAudioRef} hidden preload="metadata" />
{/* Full Screen Player */}
<FullScreenPlayer

View File

@@ -0,0 +1,50 @@
"use client"
import posthog from "posthog-js"
import { PostHogProvider as PHProvider, usePostHog } from "posthog-js/react"
import { Suspense, useEffect } from "react"
import { usePathname, useSearchParams } from "next/navigation"
function PathnameTracker() {
const posthogClient = usePostHog()
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
if (posthogClient) {
posthogClient.capture('$pageview', {
path: pathname + (searchParams.toString() ? `?${searchParams.toString()}` : ''),
})
}
}, [posthogClient, pathname, searchParams])
return null
}
function SuspendedPostHogPageView() {
return (
<Suspense fallback={null}>
<PathnameTracker />
</Suspense>
)
}
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: 'history_change',
capture_pageleave: true,
capture_exceptions: true,
debug: process.env.NODE_ENV === "development",
})
}, [])
return (
<PHProvider client={posthog}>
<SuspendedPostHogPageView />
{children}
</PHProvider>
)
}

View File

@@ -5,6 +5,7 @@ import { AudioPlayerProvider } from "./components/AudioPlayerContext";
import { NavidromeProvider } from "./components/NavidromeContext";
import { NavidromeConfigProvider } from "./components/NavidromeConfigContext";
import { ThemeProvider } from "./components/ThemeProvider";
import { PostHogProvider } from "./components/PostHogProvider";
import { Metadata } from "next";
import type { Viewport } from 'next';
import Ihateserverside from './components/ihateserverside';
@@ -75,17 +76,19 @@ export default function Layout({ children }: LayoutProps) {
/>
</head>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased bg-background`}>
<ThemeProvider>
<NavidromeConfigProvider>
<NavidromeProvider>
<AudioPlayerProvider>
<Ihateserverside>
{children}
</Ihateserverside>
</AudioPlayerProvider>
</NavidromeProvider>
</NavidromeConfigProvider>
</ThemeProvider>
<PostHogProvider>
<ThemeProvider>
<NavidromeConfigProvider>
<NavidromeProvider>
<AudioPlayerProvider>
<Ihateserverside>
{children}
</Ihateserverside>
</AudioPlayerProvider>
</NavidromeProvider>
</NavidromeConfigProvider>
</ThemeProvider>
</PostHogProvider>
</body>
</html>
);

11
lib/posthog.ts Normal file
View File

@@ -0,0 +1,11 @@
import { PostHog } from "posthog-node"
export default function PostHogClient() {
const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
capture_pageview: 'history_change',
flushAt: 1,
flushInterval: 0,
})
return posthogClient
}

View File

@@ -46,6 +46,24 @@ const nextConfig = {
},
];
},
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*',
},
{
source: '/ingest/decide',
destination: 'https://us.i.posthog.com/decide',
},
];
},
// This is required to support PostHog trailing slash API requests
skipTrailingSlashRedirect: true,
};
export default nextConfig;
export default nextConfig;

View File

@@ -31,6 +31,8 @@
"colorthief": "^2.6.0",
"lucide-react": "^0.469.0",
"next": "^15.0.3",
"posthog-js": "^1.255.0",
"posthog-node": "^5.1.1",
"react": "^19",
"react-dom": "^19",
"react-hook-form": "^7.53.2",

50
pnpm-lock.yaml generated
View File

@@ -74,6 +74,12 @@ importers:
next:
specifier: ^15.0.3
version: 15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
posthog-js:
specifier: ^1.255.0
version: 1.255.0
posthog-node:
specifier: ^5.1.1
version: 5.1.1
react:
specifier: ^19
version: 19.0.0
@@ -1487,6 +1493,9 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
core-js@3.43.0:
resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==}
create-jest@29.7.0:
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -1822,6 +1831,9 @@ packages:
fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
fflate@0.4.8:
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -2715,6 +2727,24 @@ packages:
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
engines: {node: ^10 || ^12 || >=14}
posthog-js@1.255.0:
resolution: {integrity: sha512-2ZZKrGB1Ih425IoPvmiDYN+BcDJvNJvVGRrey2ARR4UJ85oB+sNCJAx6DuwIlvsIQTe8QjuUhxrHlxAT5/7IMA==}
peerDependencies:
'@rrweb/types': 2.0.0-alpha.17
rrweb-snapshot: 2.0.0-alpha.17
peerDependenciesMeta:
'@rrweb/types':
optional: true
rrweb-snapshot:
optional: true
posthog-node@5.1.1:
resolution: {integrity: sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==}
engines: {node: '>=20'}
preact@10.26.9:
resolution: {integrity: sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -3226,6 +3256,9 @@ packages:
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
web-vitals@4.2.4:
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@@ -4806,6 +4839,8 @@ snapshots:
convert-source-map@2.0.0: {}
core-js@3.43.0: {}
create-jest@29.7.0(@types/node@22.10.5):
dependencies:
'@jest/types': 29.6.3
@@ -5283,6 +5318,8 @@ snapshots:
dependencies:
bser: 2.1.1
fflate@0.4.8: {}
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -6350,6 +6387,17 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
posthog-js@1.255.0:
dependencies:
core-js: 3.43.0
fflate: 0.4.8
preact: 10.26.9
web-vitals: 4.2.4
posthog-node@5.1.1: {}
preact@10.26.9: {}
prelude-ls@1.2.1: {}
pretty-format@29.7.0:
@@ -6937,6 +6985,8 @@ snapshots:
dependencies:
makeerror: 1.0.12
web-vitals@4.2.4: {}
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0