Skip to content

feat: growth nav, blog CTAs, analytics fixes - #3153

Merged
smakosh merged 4 commits into
mainfrom
feat/growth-nav-blog-analytics
Jul 20, 2026
Merged

smakosh merged 4 commits into
mainfrom
feat/growth-nav-blog-analytics

Conversation

@smakosh

@smakosh smakosh commented Jul 20, 2026 •

Copy link
Copy Markdown
Member

Summary

Four growth/measurement workstreams driven by last week's traffic report analysis:

1. Navbar: surface the most-clicked destinations (+ fix broken dropdowns)

  • DevPass, Chat, and Models are now direct navbar links (per PostHog 30d traffic: DevPass home 2,949 visitors/mo, Models 2,352, Chat 926), no hover needed. Dropdowns remain for the long tail; mobile menu updated to match.
  • Responsive budget at the 1192px nav: breakpoint: Chat shows ≥1360px, GitHub-stars/Discord cluster shows ≥1280px.
  • All nav links (top-level + dropdown items) fire nav_link_clicked, so future nav decisions can use real click data.
  • Fixes a dropdown regression that is live on production: NavigationMenuLink's flex-col base class (concatenated via Radix Slot) stacked every dropdown card vertically, blowing panels up to ~650px tall. One load-bearing flex-row restores 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

  • New BlogCta component with a passport/boarding-pass DevPass variant and a generic gateway variant, registered as a markdown-to-jsx override — any post can embed <BlogCta variant="devpass" />.
  • Embedded twice in best-ai-coding-plans (top post: 328 readers/wk, 2 signups). Clicks fire cta_clicked with location/cta/post; links carry UTM params for per-post conversion attribution.

3. Analytics reliability

  • Bounded PostHog init in ui + playground: captures fired before the previously-unbounded idle-deferred init() were silently dropped — the cause of playground_chat_sent zero-event days.
  • First-party /ingest proxy (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.
  • Weekly traffic report: cta_clicked/pricing_plan_clicked now 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).
  • Payer attribution (0 payers on every page in the weekly report) needed no change here — root cause was fixed by fix(api): attribute purchase events to users #3151.

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 for reset_pass_purchased.
  • reset_pass_redeemed captured server-side in the redeem route (previously emitted nothing).
  • Follow-ups after deploy: build the cap-hit → purchase funnel + cap-hit trend insights in PostHog; SQL gap-check in ~2 weeks to size the never-visits-dashboard cohort before deciding on gateway-side capture + cap-hit lifecycle email.

Testing

  • turbo run build passes for ui, playground, code, api, and scripts.
  • All 32 dev-plans-reset-passes unit tests pass.
  • Navbar verified via Playwright screenshots at 1200/1440/1550px (light + dark); dropdown panel bounds verified on-screen for Products/Resources/AI at 1200px and 1550px; blog CTAs verified in light + dark.

https://claude.ai/code/session_01FYyCRLR4CU4FWoiSi7hd1K

Summary by CodeRabbit

  • New Features

    • Added reusable promotional cards to blog content, including links for creating an account, browsing models, and exploring DevPass plans.
    • Added contextual calls to action after rankings and near the end of relevant articles.
    • Improved navigation links across desktop and mobile layouts, including access to DevPass, Chat, Models, Docs, and Pricing.
  • Analytics

    • Improved measurement of navigation, promotional interactions, usage-limit views, and plan activity.

smakosh added 4 commits July 20, 2026 21:11
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
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

PostHog tracking is added to Reset Pass redemption, usage-cap views, blog CTAs, and navigation. Client ingestion is routed through local /ingest rewrites, initialization deferral is bounded, and traffic reports support unique-person counts for selected events.

Changes

PostHog analytics

Layer / File(s) Summary
Local PostHog ingestion
apps/playground/next.config.ts, apps/ui/next.config.ts, apps/*/src/components/providers.tsx
PostHog clients send events through /ingest, Next.js rewrites ingestion routes to PostHog, and deferred initialization uses bounded scheduling.
Redemption and usage events
apps/api/src/routes/dev-plans.ts, apps/code/src/app/dashboard/components/UsageOverview.tsx
Reset Pass redemption and weekly premium-cap views capture PostHog events with their associated user, organization, tier, and usage properties.
CTA and navigation tracking
apps/ui/src/components/blog/*, apps/ui/src/components/landing/navbar.tsx, apps/ui/src/content/blog/*, apps/ui/src/lib/utils/markdown.tsx
Blog CTA variants and navigation links capture click events; markdown overrides and blog content render the CTA components.
Unique-event reporting
packages/scripts/src/traffic-report.ts
Selected events use distinct-person counts, while other events continue using total hits.

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
Loading

Possibly related PRs

Suggested reviewers: steebchen

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes around growth navigation, blog CTAs, and analytics improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/growth-nav-blog-analytics

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Mobile 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 no trackNav wiring, so mobile clicks on these three links go untracked.

🔧 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>)

Based on the PR objective: "tracks all navigation clicks" — the mobile Pricing/Docs/Models links appear to be an unintended gap in that coverage.
🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 52c1701 and a4ee447.

📒 Files selected for processing (11)
  • apps/api/src/routes/dev-plans.ts
  • apps/code/src/app/dashboard/components/UsageOverview.tsx
  • apps/playground/next.config.ts
  • apps/playground/src/components/providers.tsx
  • apps/ui/next.config.ts
  • apps/ui/src/components/blog/blog-cta.tsx
  • apps/ui/src/components/landing/navbar.tsx
  • apps/ui/src/components/providers.tsx
  • apps/ui/src/content/blog/2026-06-22-best-ai-coding-plans.md
  • apps/ui/src/lib/utils/markdown.tsx
  • packages/scripts/src/traffic-report.ts

Comment on lines +19 to +29
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,
});
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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'
fi

Repository: 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:


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.

Comment on lines +469 to +491
<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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

@smakosh
smakosh added this pull request to the merge queue Jul 20, 2026
Merged via the queue into main with commit 4678e50 Jul 20, 2026
12 checks passed
@smakosh
smakosh deleted the feat/growth-nav-blog-analytics branch July 20, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant