Implementing PostHog Tracking and Web Vitals with Next.js
TL;DR: I integrated PostHog tracking and web vitals into the lavanderia-crm project, replacing deprecated onFID with onINP and setting up a reverse proxy via /ingest rewrite. This enhances analytics and performance monitoring.
The Problem
The lavanderia-crm project needed improved analytics and performance tracking. The existing web vitals setup used the deprecated onFID metric, which was replaced by onINP in web-vitals v4. Additionally, I wanted to integrate PostHog for comprehensive tracking and engagement metrics.
What I Tried First
Initially, I focused on updating the web vitals metrics. I imported the web-vitals package and used the onFID function, which was deprecated. This approach failed because it caused compatibility issues with the latest version of web-vitals.
The Implementation
Step 1: Add PostHog Tracking
I started by installing posthog-js and adding it to the project:
// apps/web/package.json
"dependencies": {
// ...
"posthog-js": "^1.396.6",
// ...
}
Then, I created a PostHogProvider component:
// apps/web/src/app/providers/PostHogProvider.tsx
'use client'
import posthog from 'posthog-js'
import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { useEffect } from 'react'
function PostHogPageView() {
useEffect(() => {
if (!client || typeof window === 'undefined') return
// ...
}, [client])
// Web Vitals
useEffect(() => {
if (!client || typeof window === 'undefined') return
import('web-vitals').then(({ onCLS, onFCP, onINP }) => {
onCLS((metric) => {
posthog.capture('web_vitals', {
metric: 'CLS',
value: metric.value,
})
})
onFCP((metric) => {
posthog.capture('web_vitals', {
metric: 'FCP',
value: metric.value,
})
})
onINP((metric) => {
posthog.capture('web_vitals', {
metric: 'INP',
value: metric.value,
})
})
})
}, [client])
return <PHProvider client={client}>{/* children */}</PHProvider>
}
Step 2: Configure Web Vitals and Reverse Proxy
Next, I updated next.config.js to include a reverse proxy via /ingest rewrite:
// apps/web/next.config.js
const nextConfig = {
// ...
async rewrites() {
return [
{
source: '/ingest',
destination: 'https://app.posthog.com',
},
]
},
}
I also updated the web-vitals package to v5 and replaced onFID with onINP:
// apps/web/src/app/providers/PostHogProvider.tsx
- import('web-vitals').then(({ onCLS, onFID, onFCP, onINP }) => {
- onFID((metric) => {
- posthog.capture('web_vitals', {
- metric: 'FID',
- value: metric.value,
- })
- })
+ import('web-vitals').then(({ onCLS, onFCP, onINP }) => {
Step 3: Fix Page Leave Event
To properly track page leave events, I updated the usePathname hook:
// apps/web/src/app/providers/PostHogProvider.tsx
import { usePathname } from 'next/navigation'
import { useEffect } from 'react'
function PostHogPageView() {
const pathname = usePathname()
useEffect(() => {
if (!client || typeof window === 'undefined') return
const handler = () => {
posthog.capture('page_leave', {
pathname,
})
}
window.addEventListener('beforeunload', handler)
return () => window.removeEventListener('beforeunload', handler)
}, [client, pathname])
// ...
}
Key Takeaway
The key takeaway from this implementation is the importance of keeping up with deprecations and updating metrics accordingly. Replacing onFID with onINP and integrating PostHog for comprehensive tracking significantly enhances the project's analytics and performance monitoring.
What's Next
Next, I plan to explore more PostHog features, such as custom event tracking and analytics dashboards, to further improve the project's insights and performance. Additionally, I will monitor the impact of these changes on the project's performance and make adjustments as needed.
vibecoding #buildinpublic #posthog #webvitals #nextjs
Part of my Build in Public series — sharing the real process of building Building Lavandería CRM from Playa del Carmen, México.
Repo: zaerohell/lavanderia-crm · 2026-07-04
#playadev #buildinpublic
Top comments (0)