Skip to content

[CI] (gpt54-trial-fill) next-js/15-app-router-saas#2406

Closed
wizard-ci-bot[bot] wants to merge 1 commit into
mainfrom
wizard-ci-gpt54-trial-fill-next-js-15-app-router-saas
Closed

[CI] (gpt54-trial-fill) next-js/15-app-router-saas#2406
wizard-ci-bot[bot] wants to merge 1 commit into
mainfrom
wizard-ci-gpt54-trial-fill-next-js-15-app-router-saas

Conversation

@wizard-ci-bot

@wizard-ci-bot wizard-ci-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

Automated wizard CI run

Source: manual
Trigger ID: gpt54-trial-fill
App: next-js/15-app-router-saas
App directory: apps/next-js/15-app-router-saas
Workbench branch: wizard-ci-gpt54-trial-fill-next-js-15-app-router-saas
Wizard branch: 543fbd70b7d834c9bb2bf5afe28fc0a5761ced5d
Context Mill branch: main
PostHog (MCP) branch: master
Timestamp: 2026-07-03T16:29:53.301Z
Duration: 334.2s

YARA Scanner

✓ 153 tool calls scanned, 2 violations detected

  [BLOCKED] hardcoded_posthog_host (HIGH) — PostToolUse:Edit
  [BLOCKED] pii_in_capture_call (HIGH) — PostToolUse:Edit

No violations: ✓ 151 clean scans

⚠️ YARA violations detected — see report above

@wizard-ci-bot

wizard-ci-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown
Author

Now I have enough context. Let me produce the evaluation.


PR Evaluation Report

Summary

This PR integrates PostHog into a Next.js 15 App Router SaaS application, adding client-side initialization via instrumentation-client.ts, a server-side capture helper, a reverse proxy via Next.js rewrites, user identification, error tracking, and ~12 custom events across authentication, pricing, account management, and Stripe flows.

Files changed Lines added Lines removed
17 +302 -21

Confidence score: 4/5 👍

  • Broken reverse proxy asset host: The string replace posthogHost?.replace('i.', 'assets.i.') on https://us.i.posthog.com produces https://us.assets.i.posthog.com instead of the correct https://us-assets.i.posthog.com. Static/array asset rewrites will fail. [CRITICAL]
  • Mismatched distinct_ids between client and server: Client-side identify() uses user.email as distinct_id while server-side captureServerEvent() uses user.id.toString(). These are different identifiers for the same user, causing fragmented person profiles and broken analytics. [CRITICAL]
  • Email used as distinct_id: Using raw email addresses as distinct_id is explicitly discouraged by PostHog best practices. It leaks PII into the distinct_id field and causes data quality issues. [CRITICAL]
  • useEffect for event capture violates React patterns: Several components use useEffect reacting to state changes (state.success, isDeletePending) to fire posthog.capture(). React framework rules say to put analytics capture in event handlers, not in useEffect responding to state. [MEDIUM]
  • Premature identify on login click: login.tsx calls posthog.identify(email) in the submit button's onClick before the form submission succeeds—if login fails, the user is still identified with an unvalidated email. [MEDIUM]
  • Duplicate event names across client and server: password_updated, account_updated, and account_deletion_requested are captured both client-side and server-side with identical event names, double-counting these actions. [MEDIUM]

File changes

Filename Score Description
instrumentation-client.ts 5/5 Correct Next.js 15.3+ pattern for PostHog init with env vars, reverse proxy host, error tracking, and debug mode
lib/posthog-server.ts 4/5 Singleton server client with flush settings; shuts down after each call (functional but creates/destroys client per event)
next.config.ts 2/5 Reverse proxy rewrites have broken asset host derivation; removed existing config comments
app/(dashboard)/layout.tsx 2/5 Uses email as distinct_id; sign-out capture and reset are correctly placed in event handler
app/(login)/login.tsx 2/5 Premature identify on click before form succeeds; email as distinct_id
app/(login)/actions.ts 4/5 Well-placed server-side events with relevant properties; uses user.id (correct)
app/(dashboard)/dashboard/general/page.tsx 3/5 useEffect anti-pattern for capture; duplicate event name with server
app/(dashboard)/dashboard/security/page.tsx 3/5 useEffect anti-patterns for capture; duplicate event names with server
app/(dashboard)/pricing/submit-button.tsx 4/5 Good event handler placement for pricing CTA click
app/api/stripe/checkout/route.ts 5/5 Well-structured checkout completion event with relevant Stripe properties
app/api/stripe/webhook/route.ts 4/5 Uses Stripe customer ID as distinctId (not user ID) — may not link to user profile
lib/payments/actions.ts 5/5 Clean server-side checkout session event with proper user context
package.json 5/5 Both posthog-js and posthog-node correctly added
.env.example 5/5 Both env vars documented
posthog-setup-report.md 3/5 Unnecessary file; not harmful but adds noise to the codebase

App sanity check ⚠️

Criteria Result Description
App builds and runs Yes No syntax or type errors that would prevent build
Preserves existing env vars & configs Yes Existing env vars preserved; original config comments removed but non-functional
No syntax or type errors Yes All code is syntactically valid TypeScript/TSX
Correct imports/exports Yes posthog-js used on client, posthog-node on server; all imports resolve
Minimal, focused changes No posthog-setup-report.md is unnecessary scope creep
Pre-existing issues None No pre-existing issues observed

Issues

  • Unnecessary report file: posthog-setup-report.md is generated documentation that doesn't belong in the codebase as committed code. [LOW]
  • Removed config comments: Original Next.js config comments about PPR/canary were removed from next.config.ts. [LOW]

Other completed criteria

  • Environment variables documented in both .env.example and README.md
  • Build configuration is valid with proper package.json entries
  • Both SDKs correctly added to dependencies

PostHog implementation ❌

Criteria Result Description
PostHog SDKs installed Yes posthog-js@^1.396.6 and posthog-node@^5.39.4 added to package.json
PostHog client initialized Yes instrumentation-client.ts uses posthog.init() with env vars, correct Next.js 15.3+ pattern
capture() Yes 12 meaningful events across client and server
identify() No Uses user.email as distinct_id on client, user.id.toString() on server — mismatched identifiers and PII as distinct_id
Error tracking Yes capture_exceptions: true in posthog.init() config
Reverse proxy No Asset host derivation is broken: .replace('i.', 'assets.i.') produces us.assets.i.posthog.com instead of us-assets.i.posthog.com

Issues

  • Broken reverse proxy asset host derivation: posthogHost?.replace('i.', 'assets.i.') on https://us.i.posthog.com produces https://us.assets.i.posthog.com (period-separated) instead of the correct https://us-assets.i.posthog.com (hyphen-separated). The /ingest/static/* and /ingest/array/* rewrites will fail to reach PostHog's CDN, breaking SDK asset loading through the proxy. Fix: use posthogHost?.replace('.i.posthog.com', '-assets.i.posthog.com') or hardcode the correct asset host. [CRITICAL]
  • Mismatched distinct_ids: Client-side uses user.email as distinct_id in posthog.identify(), while server-side uses user.id.toString() in captureServerEvent(). PostHog will create separate person profiles for the same user, fragmenting analytics data across client and server events. Fix: use user.id.toString() consistently everywhere, or pass user ID to client components. [CRITICAL]
  • Email as distinct_id: Using raw email as the PostHog distinct_id leaks PII into a system identifier field and is explicitly discouraged. The app has numeric user IDs available via user.id — these should be used instead. [CRITICAL]
  • Premature identify on login: In login.tsx, posthog.identify(email) is called in the submit button's onClick handler before the form submission completes. If authentication fails, PostHog still links the anonymous session to that email. Identify should be called after successful authentication. [MEDIUM]
  • useEffect anti-patterns: general/page.tsx and security/page.tsx use useEffect to react to state.success and isDeletePending state changes for capture calls. Per React best practices, analytics should be captured in the event handler that triggers the action, not in a useEffect observing derived state. [MEDIUM]
  • Duplicate event names: password_updated, account_updated, and account_deletion_requested are captured with identical names on both client (useEffect) and server (actions.ts), double-counting these user actions. Server events for sign-in/sign-up correctly use server_ prefix but these three events don't. [MEDIUM]

Other completed criteria

  • API key loaded from NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN environment variable (not hardcoded)
  • API host correctly configured via environment variable
  • posthog.reset() correctly called on sign-out before navigation
  • Server-side client uses flushAt: 1 and flushInterval: 0 per Next.js docs
  • ui_host correctly set for reverse proxy configuration

PostHog insights and events ⚠️

Filename PostHog events Description
instrumentation-client.ts captureException (autocapture) Client-side error tracking via capture_exceptions: true
app/(login)/login.tsx user_signed_in, user_signed_up Client-side auth events with source property; also premature identify
app/(dashboard)/layout.tsx user_signed_out Sign-out event with posthog.reset(); identify on dashboard load
app/(dashboard)/dashboard/general/page.tsx account_updated Client-side account update on success state
app/(dashboard)/dashboard/security/page.tsx password_updated, account_deletion_requested Client-side security actions on state changes
app/(dashboard)/pricing/submit-button.tsx pricing_cta_clicked Plan selection with plan_name and price_id properties
app/(login)/actions.ts server_user_signed_in, server_user_signed_up, password_updated, account_updated, account_deletion_requested Server-side auth and account management events
lib/payments/actions.ts checkout_session_started Server-side checkout initiation with team and plan context
app/api/stripe/checkout/route.ts checkout_completed Checkout completion with Stripe subscription details
app/api/stripe/webhook/route.ts stripe_subscription_updated Webhook-driven subscription lifecycle events

Issues

  • Duplicate client/server events will inflate metrics: Three event names (password_updated, account_updated, account_deletion_requested) fire on both client and server, making funnel and trend analyses unreliable without filtering by source. Fix: either remove client-side duplicates or use distinct names (e.g., server_password_updated). [MEDIUM]
  • Webhook uses Stripe customer ID as distinct_id: In stripe/webhook/route.ts, String(subscription.customer) is a Stripe customer ID (e.g., cus_xxx), not a PostHog distinct_id. These events won't link to the user's profile. Fix: look up the user by Stripe customer ID and use their app user ID. [MEDIUM]

Other completed criteria

  • Events represent real user actions across the full SaaS lifecycle (auth → pricing → checkout → account management)
  • Events enable product insights: sign-up to checkout funnel is possible, retention via auth events
  • Events include contextual properties (team_id, plan_name, source, subscription_status)
  • No PII in capture() event properties — emails only appear in identify() person properties
  • Event names are descriptive, consistent snake_case convention

Reviewed by wizard workbench PR evaluator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants