Repository navigation
feat: growth nav, blog CTAs, analytics fixes - #3153
Conversation
Promote the most-visited destinations (DevPass, Chat, Models per PostHog 30d traffic) from the Products/Resources dropdowns to direct navbar links, add nav_link_clicked tracking to all nav links, and fix two dropdown bugs: NavigationMenuLink's flex-col base class stacking the icon cards vertically (also broken in production), and panels clipping off the right viewport edge (now centered under triggers). Claude-Session: https://claude.ai/code/session_01FYyCRLR4CU4FWoiSi7hd1K
New BlogCta component (passport-styled DevPass variant + generic gateway variant) registered as a markdown override, embedded twice in best-ai-coding-plans (328 readers/wk, 0.6% signup conversion). Clicks fire cta_clicked with post attribution; links carry UTM params. Claude-Session: https://claude.ai/code/session_01FYyCRLR4CU4FWoiSi7hd1K
Client events were silently dropped two ways: captures fired before the unbounded idle-deferred posthog.init (visible as zero-event days for playground_chat_sent), and ad blockers blocking *.posthog.com. Bound the init deferral (800ms timeout) and route ingestion through a first-party /ingest rewrite in ui and playground. Also make the weekly traffic report count unique persons for cta_clicked/pricing_plan_clicked so a single bot can't skew a week (708 pricing clicks from one crawler on Jul 11). Claude-Session: https://claude.ai/code/session_01FYyCRLR4CU4FWoiSi7hd1K
Close the top of the Reset Pass upsell funnel: capture devpass_weekly_cap_hit_viewed when the dashboard renders the exhausted weekly premium meter (tier, usage, passes available), and capture reset_pass_redeemed server-side in the redeem route, which previously emitted no analytics. Enables the cap-hit -> purchase conversion funnel per tier. Claude-Session: https://claude.ai/code/session_01FYyCRLR4CU4FWoiSi7hd1K
WalkthroughPostHog tracking is added to Reset Pass redemption, usage-cap views, blog CTAs, and navigation. Client ingestion is routed through local ChangesPostHog analytics
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Visitor
participant BlogCta
participant PostHogClient
participant Next.js
participant PostHogIngestion
Visitor->>BlogCta: Click CTA
BlogCta->>PostHogClient: Capture cta_clicked
PostHogClient->>Next.js: Send event to /ingest
Next.js->>PostHogIngestion: Rewrite ingestion request
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ui/src/components/landing/navbar.tsx (1)
630-657: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMobile Pricing/Docs/Models links are missing
trackNav, unlike their desktop counterparts.The PR objective states navigation clicks are now tracked everywhere, and the desktop Pricing (Line 588-599) and Docs (Line 572-586) links got
onClick={() => trackNav(...)}, and the new desktop Models link (Line 493-504) also got it. The pre-existing mobile Pricing (Line 630-638), Docs (Line 640-648), and Models (Line 650-657) list items were left unchanged with notrackNavwiring, so mobile clicks on these three links go untracked.Based on the PR objective: "tracks all navigation clicks" — the mobile Pricing/Docs/Models links appear to be an unintended gap in that coverage.🔧 Wire tracking into the mobile links
<Link href="/pricing" className="text-muted-foreground hover:text-accent-foreground block py-2.5 duration-150" prefetch={true} + onClick={() => trackNav("Pricing")} > Pricing </Link>(apply similarly to the mobile Docs
<a>and Models<Link>)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/ui/src/components/landing/navbar.tsx` around lines 630 - 657, Wire the mobile Pricing, Docs, and Models links in the navbar to invoke the existing trackNav handler, matching their corresponding desktop links. Add the appropriate onClick tracking to the Pricing Link, Docs anchor, and Models Link while preserving their current destinations and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/ui/src/components/blog/blog-cta.tsx`:
- Around line 19-29: Update the track handler to guard the posthog capture call
when usePostHog() returns undefined during initialization, using optional
chaining or an equivalent if check while preserving the existing event payload.
In `@apps/ui/src/components/landing/navbar.tsx`:
- Around line 469-491: Add target="_blank" and rel="noopener noreferrer" to the
DevPass and Chat anchor elements in the desktop NavigationMenuItem entries and
their corresponding mobile entries, preserving the existing hrefs and tracking
handlers so both top-level links match the behavior of the
IconMenuItem/mobileSections variants.
---
Outside diff comments:
In `@apps/ui/src/components/landing/navbar.tsx`:
- Around line 630-657: Wire the mobile Pricing, Docs, and Models links in the
navbar to invoke the existing trackNav handler, matching their corresponding
desktop links. Add the appropriate onClick tracking to the Pricing Link, Docs
anchor, and Models Link while preserving their current destinations and
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: f68d4eeb-cde0-45bf-aab8-ec8ca4c3bee4
📒 Files selected for processing (11)
apps/api/src/routes/dev-plans.tsapps/code/src/app/dashboard/components/UsageOverview.tsxapps/playground/next.config.tsapps/playground/src/components/providers.tsxapps/ui/next.config.tsapps/ui/src/components/blog/blog-cta.tsxapps/ui/src/components/landing/navbar.tsxapps/ui/src/components/providers.tsxapps/ui/src/content/blog/2026-06-22-best-ai-coding-plans.mdapps/ui/src/lib/utils/markdown.tsxpackages/scripts/src/traffic-report.ts
| const posthog = usePostHog(); | ||
| const pathname = usePathname(); | ||
| const post = pathname?.split("/").pop() ?? "unknown"; | ||
|
|
||
| const track = (cta: string) => { | ||
| posthog.capture("cta_clicked", { | ||
| location: `blog_${location}`, | ||
| cta, | ||
| post, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== blog-cta.tsx ==\n'
cat -n apps/ui/src/components/blog/blog-cta.tsx | sed -n '1,120p'
printf '\n== search usePostHog/capture ==\n'
rg -n "usePostHog|posthog\.capture|posthog\?\.\s*capture|PostHogProvider|ready" apps/ui/src -g '!**/node_modules/**'
printf '\n== navbar.tsx ==\n'
if [ -f apps/ui/src/components/navbar.tsx ]; then
cat -n apps/ui/src/components/navbar.tsx | sed -n '1,220p'
fiRepository: theopenco/llmgateway
Length of output: 45642
🌐 Web query:
PostHog React usePostHog capture before provider initialization undefined TypeError docs
💡 Result:
The TypeError: Cannot read properties of undefined (reading '...') when using the PostHog React usePostHog hook typically occurs because you are attempting to access or call methods on the posthog instance before it has fully initialized [1][2]. This often happens during the initial component render [1][2]. To resolve this issue, use the following approaches recommended by PostHog documentation: 1. Use Optional Chaining: Use the optional chaining operator (?.) when calling methods on the posthog instance returned by the usePostHog hook [1][2]. This prevents the code from throwing an error if the instance is currently undefined. posthog?.capture('event_name') 2. Check for Initialization: Wrap your calls in a conditional check to ensure the instance exists before executing any methods [1][2]. useEffect( => { if (posthog) { posthog.capture('event_name'); } }, [posthog]); 3. Ensure Correct Provider Placement: Verify that your application is wrapped by the PostHogProvider at the root of your application (e.g., in main.tsx or App.tsx) and that it is provided with a valid apiKey or a pre-initialized client [3][4][5]. Avoid importing the posthog-js singleton directly in your components for React-specific logic, as this bypasses the React context and can lead to uninitialized state errors [1][2]. If you are manually passing a client instance to the PostHogProvider, ensure you are handling the lifecycle of that client correctly, as the provider assumes you are controlling its initialization [6]. Always prefer the usePostHog hook for accessing the instance within components [1][3].
Citations:
- 1: https://posthog.com/docs/libraries/react
- 2: https://posthog.com/docs/libraries/react.md
- 3: https://posthog.com/docs/web-analytics/installation/react
- 4: https://github.com/PostHog/posthog-js/blob/main/packages/react/README.md
- 5: https://posthog.com/docs/feature-flags/installation/react
- 6: Uninitialized PostHog client scenario when initializing PostHogProvider with client PostHog/posthog-js#769
Guard capture behind the PostHog instance. usePostHog() can be undefined while the provider is still initializing, so this click handler should use posthog?.capture(...) or an if (posthog) check.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/ui/src/components/blog/blog-cta.tsx` around lines 19 - 29, Update the
track handler to guard the posthog capture call when usePostHog() returns
undefined during initialization, using optional chaining or an equivalent if
check while preserving the existing event payload.
| <NavigationMenuItem> | ||
| <NavigationMenuLink asChild> | ||
| <a | ||
| href="https://devpass.llmgateway.io" | ||
| onClick={() => trackNav("DevPass")} | ||
| className="text-muted-foreground hover:text-accent-foreground block duration-150 px-3 py-2 whitespace-nowrap" | ||
| > | ||
| DevPass | ||
| </a> | ||
| </NavigationMenuLink> | ||
| </NavigationMenuItem> | ||
|
|
||
| <NavigationMenuItem className="hidden min-[1360px]:block"> | ||
| <NavigationMenuLink asChild> | ||
| <a | ||
| href={config.playgroundUrl ?? "#"} | ||
| onClick={() => trackNav("Chat")} | ||
| className="text-muted-foreground hover:text-accent-foreground block duration-150 px-3 py-2 whitespace-nowrap" | ||
| > | ||
| Chat | ||
| </a> | ||
| </NavigationMenuLink> | ||
| </NavigationMenuItem> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
DevPass/Chat top-level links skip target="_blank" rel="noopener noreferrer" that the same destinations use elsewhere.
The new promoted DevPass/Chat links (desktop NavigationMenuItems at Line 471-491 and mobile <li> entries at Line 612-629) open https://devpass.llmgateway.io / config.playgroundUrl in the same tab. The identical destinations rendered via IconMenuItem (Products dropdown, external: true) and via mobileSections open with target="_blank" rel="noopener noreferrer". Clicking "DevPass" now behaves differently depending on which nav entry point is used.
🔧 Align behavior with the dropdown/mobile-section variants
<a
href="https://devpass.llmgateway.io"
+ target="_blank"
+ rel="noopener noreferrer"
onClick={() => trackNav("DevPass")}
className="text-muted-foreground hover:text-accent-foreground block duration-150 px-3 py-2 whitespace-nowrap"
>
DevPass
</a>(apply the same to the Chat anchor at both desktop and mobile locations)
Also applies to: 612-629
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/ui/src/components/landing/navbar.tsx` around lines 469 - 491, Add
target="_blank" and rel="noopener noreferrer" to the DevPass and Chat anchor
elements in the desktop NavigationMenuItem entries and their corresponding
mobile entries, preserving the existing hrefs and tracking handlers so both
top-level links match the behavior of the IconMenuItem/mobileSections variants.
Summary
Four growth/measurement workstreams driven by last week's traffic report analysis:
1. Navbar: surface the most-clicked destinations (+ fix broken dropdowns)
nav:breakpoint: Chat shows ≥1360px, GitHub-stars/Discord cluster shows ≥1280px.nav_link_clicked, so future nav decisions can use real click data.NavigationMenuLink'sflex-colbase class (concatenated via Radix Slot) stacked every dropdown card vertically, blowing panels up to ~650px tall. One load-bearingflex-rowrestores the layout (~430px). Panels also now center under their triggers instead of clipping off the right viewport edge on <1600px screens.2. Blog → signup CTAs
BlogCtacomponent with a passport/boarding-pass DevPass variant and a generic gateway variant, registered as amarkdown-to-jsxoverride — any post can embed<BlogCta variant="devpass" />.best-ai-coding-plans(top post: 328 readers/wk, 2 signups). Clicks firecta_clickedwithlocation/cta/post; links carry UTM params for per-post conversion attribution.3. Analytics reliability
init()were silently dropped — the cause ofplayground_chat_sentzero-event days./ingestproxy (Next rewrites →us.i.posthog.com) so ad blockers stop eating client events. Expect a one-time step up in client-event counts after deploy — recovered data, not growth.cta_clicked/pricing_plan_clickednow count unique persons ("CTA clickers"/"Pricing clickers") so one bot can't skew a week (708 pricing clicks from a single crawler on Jul 11).4. Reset Pass upsell funnel instrumentation
devpass_weekly_cap_hit_viewed(dashboard, on exhausted weekly premium meter) with tier/usage/passes-available properties — the funnel top forreset_pass_purchased.reset_pass_redeemedcaptured server-side in the redeem route (previously emitted nothing).Testing
turbo run buildpasses for ui, playground, code, api, and scripts.dev-plans-reset-passesunit tests pass.https://claude.ai/code/session_01FYyCRLR4CU4FWoiSi7hd1K
Summary by CodeRabbit
New Features
Analytics