Skip to content

[CI] (d819646) next-js/15-app-router-saas#1497

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

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

Conversation

@wizard-ci-bot
Copy link
Copy Markdown

@wizard-ci-bot wizard-ci-bot Bot commented May 14, 2026

Automated wizard CI run

Source: manual
Trigger ID: d819646
App: next-js/15-app-router-saas
App directory: apps/next-js/15-app-router-saas
Workbench branch: wizard-ci-d819646-next-js-15-app-router-saas
Wizard branch: main
Context Mill branch: main
PostHog (MCP) branch: master
Timestamp: 2026-05-14T16:44:43.440Z
Duration: 817.9s

@wizard-ci-bot
Copy link
Copy Markdown
Author

wizard-ci-bot Bot commented May 14, 2026

PR Evaluation Report

Summary

This PR integrates PostHog into a Next.js 15 App Router SaaS application with both client-side (posthog-js via instrumentation-client.ts) and server-side (posthog-node) tracking. It adds a reverse proxy, error tracking, user identification, and captures 12 meaningful business events across authentication, payments, and team management flows.

Files changed Lines added Lines removed
12 +221 -3

Confidence score: 4/5 👍

  • Client-side identify uses raw email as distinct_id while server-side uses numeric user.id. This causes identity fragmentation — PostHog will treat the same user as two separate people. The client-side posthog.identify(email, { email }) in login.tsx must use the same identifier as the server (String(user.id)). [CRITICAL]
  • Webhook events use fabricated distinct_id (stripe_customer_) that will never link to an actual user profile, creating orphaned events. Should resolve the Stripe customer to the actual user ID from the database. [MEDIUM]
  • No .env.example file committed.env.local is gitignored (correctly), but there's no .env.example to document required environment variables for other developers. The setup report mentions them, but this isn't a standard pattern. [LOW]

File changes

Filename Score Description
instrumentation-client.ts 5/5 Correct Next.js 15.3+ client-side init with reverse proxy, error tracking, and defaults
lib/posthog-server.ts 5/5 Server-side client factory with proper flushAt/flushInterval for short-lived functions
next.config.ts 5/5 Reverse proxy with all three required rewrite rules + skipTrailingSlashRedirect
package.json 5/5 Both posthog-js and posthog-node added
app/(login)/actions.ts 4/5 Comprehensive server-side event capture; identify uses correct numeric user ID
app/(login)/login.tsx 2/5 Client-side identify uses raw email as distinct_id, mismatching server-side user ID
app/(dashboard)/layout.tsx 5/5 Correct posthog.reset() on sign-out
app/api/stripe/checkout/route.ts 4/5 Good checkout_completed event with relevant properties
app/api/stripe/webhook/route.ts 3/5 Fabricated distinct_id won't link to actual users
lib/payments/actions.ts 5/5 Clean checkout_initiated capture with user context
.gitignore 5/5 Correctly ignores .env.local
posthog-setup-report.md 5/5 Thorough documentation of all changes

App sanity check ✅

Criteria Result Description
App builds and runs Yes No syntax errors, valid TypeScript, all imports resolve
Preserves existing env vars & configs Yes Existing next.config.ts extended cleanly, no existing code deleted
No syntax or type errors Yes All files have valid syntax and correct types
Correct imports/exports Yes posthog-js used client-side only, posthog-node server-side only
Minimal, focused changes Yes All changes directly relate to PostHog integration
Pre-existing issues None Base app appears healthy

Issues

  • No .env.example file: Environment variables are documented only in the setup report, not in a committed .env.example file. Add .env.example with placeholder values. [LOW]

Other completed criteria

  • Build configuration is valid — package.json additions are correct
  • Existing app functionality preserved — sign-out, sign-in, checkout flows all intact
  • No unrelated modifications or gratuitous reformatting

PostHog implementation ⚠️

Criteria Result Description
PostHog SDKs installed Yes posthog-js@^1.373.4 and posthog-node@^5.34.1 in package.json
PostHog client initialized Yes Client via instrumentation-client.ts (correct for Next.js 15.3+), server via getPostHogClient() factory with flushAt: 1, flushInterval: 0
capture() Yes 12 meaningful events across auth, payments, and team management
identify() No Client-side uses raw email as distinct_id; server-side uses String(user.id) — identity mismatch causes fragmented user profiles
Error tracking Yes capture_exceptions: true in posthog.init() enables automatic exception capture
Reverse proxy Yes Three rewrite rules in next.config.ts: /ingest/static/* and /ingest/array/*us-assets.i.posthog.com, /ingest/*us.i.posthog.com. skipTrailingSlashRedirect: true included.

Issues

  • Client/server distinct_id mismatch: In login.tsx, posthog.identify(email, { email }) uses the user's email as distinct_id, while all server-side calls use String(user.id) (numeric database ID). PostHog will create two separate person profiles for the same user. Fix: the client-side identify should use the same numeric user ID returned from the server after successful authentication, or the server should alias the email to the user ID. [CRITICAL]
  • Webhook fabricated distinct_id: app/api/stripe/webhook/route.ts uses stripe_customer_ as distinctId. This synthetic ID won't merge with actual user profiles, making these events (subscription_updated, subscription_cancelled) invisible in user-level analysis. Fix: look up the user by their Stripe customer ID from the teams/users table and use the actual user ID. [MEDIUM]

Other completed criteria

  • API key loaded from environment variable NEXT_PUBLIC_POSTHOG_KEY
  • Host correctly configured — client uses /ingest reverse proxy, server uses NEXT_PUBLIC_POSTHOG_HOST
  • posthog.reset() correctly called on sign-out in layout.tsx
  • Server-side posthog.shutdown() called after every capture block
  • ui_host set to https://us.posthog.com for toolbar/survey access through reverse proxy

PostHog insights and events ✅

Filename PostHog events Description
app/(login)/actions.ts user_signed_in, user_signed_up, user_signed_out, password_updated, account_deleted, account_updated, team_member_removed, team_member_invited Core auth and account lifecycle events with server-side capture. Includes via_invitation, role, removed_member_id properties.
app/(login)/login.tsx identify (client) Client-side identify on form submit (but uses email as distinct_id)
lib/payments/actions.ts checkout_initiated Captures start of Stripe checkout flow with plan_name and price_id
app/api/stripe/checkout/route.ts checkout_completed Captures successful checkout with plan_name, subscription_status, stripe_subscription_id
app/api/stripe/webhook/route.ts subscription_updated, subscription_cancelled Webhook-driven subscription lifecycle events
app/(dashboard)/layout.tsx reset Clears PostHog identity on sign-out

Issues

  • Webhook events orphaned from users: subscription_updated and subscription_cancelled use stripe_customer_ as distinct_id, so these events won't appear in any real user's event stream. [MEDIUM]

Other completed criteria

  • Events represent real user actions across the full SaaS lifecycle (signup → checkout → subscription management → churn)
  • Events enable product insights — signup-to-checkout funnel, subscription health, team engagement metrics are all possible
  • Events include relevant contextual properties (plan_name, subscription_status, role, via_invitation)
  • No PII in event properties — email and name only set via identify() person properties
  • Event names follow consistent snake_case convention with descriptive action names

Reviewed by wizard workbench PR evaluator

@wizard-ci-bot wizard-ci-bot Bot added the CI/CD label May 14, 2026
@wizard-ci-bot wizard-ci-bot Bot closed this May 14, 2026
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