feat(docs): DR-7849 DR-7850 PostHog tracking + UTM injection#7697
feat(docs): DR-7849 DR-7850 PostHog tracking + UTM injection#7697ArthurGamby wants to merge 3 commits intomainfrom
Conversation
- Add content area super properties (content_area, is_ppg_or_compute, content_subtype) - Track high-intent actions: copy prompt, copy markdown, open external tool - Track console link clicks with UTM presence - Add remark plugin for automatic UTM injection on console.prisma.io links
WalkthroughThis pull request introduces comprehensive analytics and tracking instrumentation to the documentation site. Changes include adding PostHog event capture for user interactions (prompt copying, markdown copying, external tool access), automatic UTM parameter injection for console.prisma.io links via a new remark plugin, content area classification utilities, and a new tracking provider component for monitoring link clicks and page navigation. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/docs/src/lib/tracking.ts (1)
28-31: Consider more precise matching for subtypes.Using
includes()will match the substring anywhere in the path. For example,/docs/orm/some-quickstart-guidewould match even if "quickstart" is just part of a slug. This might be intentional for broad matching, but if you want exact segment matching:♻️ Optional: Match path segments more precisely
export function getContentSubtype(pathname: string): string | null { - if (pathname.includes("/quickstart")) return "quickstart"; - if (pathname.includes("/getting-started")) return "getting-started"; + const segments = pathname.split("/"); + if (segments.includes("quickstart")) return "quickstart"; + if (segments.includes("getting-started")) return "getting-started"; return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/docs/src/lib/tracking.ts` around lines 28 - 31, The getContentSubtype function currently uses pathname.includes(...) which can match substrings inside slugs; change it to match path segments more precisely by checking the pathname segments or using anchored regexes (e.g. match "/quickstart" or "/quickstart/" and similarly for "getting-started") so only a full segment triggers; update getContentSubtype to split pathname on "/" and check segment equality or use regex like /(^|\/)quickstart(\/|$)/ and /(^|\/)getting-started(\/|$)/ to return the correct subtype.apps/docs/src/components/tracking-provider.tsx (1)
19-36: Consider stable handler reference withuseCallbackfor minor optimization.The click handler is recreated and the event listener is removed/re-added on every pathname change. While functionally correct (it ensures
pathnameis fresh in the closure), this is slightly inefficient.That said, route changes are infrequent enough that this is unlikely to cause any performance issues in practice. The current implementation is clear and correct.
♻️ Optional: Use ref to avoid re-registering listener
+"use client"; +import { useEffect, useRef } from "react"; +import { usePathname } from "next/navigation"; +import posthog from "posthog-js"; +import { getContentArea, getContentSubtype, isPpgOrCompute } from "@/lib/tracking"; + +export function TrackingProvider() { + const pathname = usePathname(); + const pathnameRef = useRef(pathname); + pathnameRef.current = pathname; + // ... first useEffect unchanged ... useEffect(() => { function handleClick(e: MouseEvent) { const anchor = (e.target as HTMLElement).closest<HTMLAnchorElement>( 'a[href*="console.prisma.io"]', ); if (!anchor) return; const url = anchor.href; posthog.capture("docs:console_link_click", { - page_path: pathname, + page_path: pathnameRef.current, destination_url: url, has_utm: url.includes("utm_source"), }); } document.addEventListener("click", handleClick); return () => document.removeEventListener("click", handleClick); - }, [pathname]); + }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/docs/src/components/tracking-provider.tsx` around lines 19 - 36, The click handler handleClick is recreated on every pathname change causing the listener to be removed/re-added; make the handler reference stable by creating a ref (e.g., pathnameRef) that you update with the latest pathname and then wrap handleClick in useCallback with an empty dependency array so the same function instance is registered once in the useEffect; inside handleClick read pathnameRef.current and call posthog.capture with destination_url and has_utm as before, and keep the document.addEventListener/removeEventListener logic using this stable handleClick to avoid re-registering on route changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/docs/src/lib/tracking.ts`:
- Around line 14-26: The function name isPpgOrCompute is misleading because
PPG_AREAS only contains "ppg" and "compute" is not a ContentArea; either rename
the function to isPpg (and update its callers and the PostHog property
is_ppg_or_compute in tracking-provider.tsx) if "compute" is intentionally
removed, or restore "compute" into PPG_AREAS (and ContentArea) if it should be
tracked separately; locate symbols isPpgOrCompute, PPG_AREAS, PREFIX_MAP and
update tracking-provider.tsx usage and the PostHog property name accordingly to
keep names consistent.
---
Nitpick comments:
In `@apps/docs/src/components/tracking-provider.tsx`:
- Around line 19-36: The click handler handleClick is recreated on every
pathname change causing the listener to be removed/re-added; make the handler
reference stable by creating a ref (e.g., pathnameRef) that you update with the
latest pathname and then wrap handleClick in useCallback with an empty
dependency array so the same function instance is registered once in the
useEffect; inside handleClick read pathnameRef.current and call posthog.capture
with destination_url and has_utm as before, and keep the
document.addEventListener/removeEventListener logic using this stable
handleClick to avoid re-registering on route changes.
In `@apps/docs/src/lib/tracking.ts`:
- Around line 28-31: The getContentSubtype function currently uses
pathname.includes(...) which can match substrings inside slugs; change it to
match path segments more precisely by checking the pathname segments or using
anchored regexes (e.g. match "/quickstart" or "/quickstart/" and similarly for
"getting-started") so only a full segment triggers; update getContentSubtype to
split pathname on "/" and check segment equality or use regex like
/(^|\/)quickstart(\/|$)/ and /(^|\/)getting-started(\/|$)/ to return the correct
subtype.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f9ec0067-9d17-4d60-b68a-ae522842ca3f
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
apps/docs/package.jsonapps/docs/source.config.tsapps/docs/src/components/page-actions.tsxapps/docs/src/components/provider.tsxapps/docs/src/components/tracking-provider.tsxapps/docs/src/lib/remark-console-utm.tsapps/docs/src/lib/tracking.ts
| const PPG_AREAS: Set<ContentArea> = new Set(["ppg"]); | ||
|
|
||
| export function getContentArea(pathname: string): ContentArea { | ||
| const stripped = pathname.replace(/^\/docs\/v\d+/, ""); | ||
| for (const [prefix, area] of PREFIX_MAP) { | ||
| if (stripped.startsWith(prefix)) return area; | ||
| } | ||
| return "other"; | ||
| } | ||
|
|
||
| export function isPpgOrCompute(area: ContentArea): boolean { | ||
| return PPG_AREAS.has(area); | ||
| } |
There was a problem hiding this comment.
Function name isPpgOrCompute may be misleading.
The function name suggests it checks for both "ppg" and "compute" areas, but PPG_AREAS only contains "ppg", and "compute" isn't in the ContentArea type.
If "compute" was intentionally removed or consolidated into "ppg", consider renaming to isPpg() for clarity. If "compute" should be tracked separately, it may need to be added.
♻️ Suggested rename if compute is not needed
-const PPG_AREAS: Set<ContentArea> = new Set(["ppg"]);
+const PPG_AREAS: Set<ContentArea> = new Set(["ppg"]);
-export function isPpgOrCompute(area: ContentArea): boolean {
+export function isPpg(area: ContentArea): boolean {
return PPG_AREAS.has(area);
}Note: You'd also need to update the call site in tracking-provider.tsx and the PostHog property name is_ppg_or_compute.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/docs/src/lib/tracking.ts` around lines 14 - 26, The function name
isPpgOrCompute is misleading because PPG_AREAS only contains "ppg" and "compute"
is not a ContentArea; either rename the function to isPpg (and update its
callers and the PostHog property is_ppg_or_compute in tracking-provider.tsx) if
"compute" is intentionally removed, or restore "compute" into PPG_AREAS (and
ContentArea) if it should be tracked separately; locate symbols isPpgOrCompute,
PPG_AREAS, PREFIX_MAP and update tracking-provider.tsx usage and the PostHog
property name accordingly to keep names consistent.
Summary
content_area,is_ppg_or_compute,content_subtype) registered on every route changeconsole.prisma.iolink clicks with UTM presence detectionutm_source,utm_medium,utm_contenton allconsole.prisma.iolinks at MDX build timeutm_source=docs-v6, v7 usesutm_source=docsutm_sourceare left untouchedLinear
Preview
Test plan
/docs/postgres/npx-create-dbhttps://console.prisma.io/?utm_source=docs&utm_medium=content&utm_content=postgres✅ If the UTM params are present, the remark plugin is working correctly.