Skip to content

[CI] (b3fa00a) next-js/15-pages-router-saas#984

Closed
wizard-ci-bot[bot] wants to merge 1 commit intomainfrom
wizard-ci-b3fa00a-next-js-15-pages-router-saas
Closed

[CI] (b3fa00a) next-js/15-pages-router-saas#984
wizard-ci-bot[bot] wants to merge 1 commit intomainfrom
wizard-ci-b3fa00a-next-js-15-pages-router-saas

Conversation

@wizard-ci-bot
Copy link

@wizard-ci-bot wizard-ci-bot bot commented Mar 24, 2026

Automated wizard CI run

Source: manual
Trigger ID: b3fa00a
App: next-js/15-pages-router-saas
App directory: apps/next-js/15-pages-router-saas
Workbench branch: wizard-ci-b3fa00a-next-js-15-pages-router-saas
Wizard branch: main
Context Mill branch: main
PostHog (MCP) branch: master
Timestamp: 2026-03-24T16:53:35.021Z
Duration: 402.4s

@wizard-ci-bot
Copy link
Author

wizard-ci-bot bot commented Mar 24, 2026

Now I have all the context I need. Let me compile my evaluation.

Key findings:

  1. Client-side identify uses email as distinct_id (line 63: posthog.identify(data.email, { email: data.email })), but server-side uses String(user.id) — this is a critical mismatch that will create fragmented user data.
  2. Email in capture event properties on both client and server — PII issue.
  3. No .env.example update — PostHog env vars not documented.
  4. No await posthog.shutdown() on server-side — but with flushAt: 1 and flushInterval: 0 on a singleton, this is mitigated since events flush immediately. Still a concern for completeness.
  5. subscription_updated uses Stripe customer ID as distinct_id — this won't match the user ID used elsewhere.
  6. invited_email PII in event properties in team invite capture.

PR Evaluation Report

Summary

This PR integrates PostHog into a Next.js 15 Pages Router SaaS application with both client-side (posthog-js) and server-side (posthog-node) instrumentation. It adds initialization via instrumentation-client.ts, a reverse proxy via Next.js rewrites, user identification, error tracking, and comprehensive event capture across auth flows, Stripe billing, and team management. However, there are critical issues with inconsistent distinct IDs between client and server, PII in event properties, and missing env documentation.

Files changed Lines added Lines removed
16 +221 -3

Confidence score: 4/5 👍

  • Distinct ID mismatch between client and server: Client-side posthog.identify() uses data.email as the distinct ID, while server-side uses String(user.id). This creates fragmented user profiles — the client will create a person keyed by email, and the server will create a separate person keyed by numeric ID. These will not merge automatically. [CRITICAL]
  • PII (email) in capture event properties: Multiple posthog.capture() calls include email as an event property (e.g., user_signed_in, user_signed_up, account_updated). Email is PII and should only be set via identify() person properties or ``, not in event properties. invited_email in `team_member_invited` is also PII. [MEDIUM]
  • Stripe webhook uses Stripe customer ID as distinct_id: In webhook.ts, the subscription_updated event uses subscription.customer (a Stripe cus_ ID) as the distinct ID, which won't match the String(user.id) used elsewhere, creating yet another orphaned user identity. [CRITICAL]
  • PostHog env vars not documented in .env.example: The .env.example file was not updated to include NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN or NEXT_PUBLIC_POSTHOG_HOST. [MEDIUM]

File changes

Filename Score Description
instrumentation-client.ts 5/5 Clean client-side PostHog init with reverse proxy host, exception capture, defaults, and debug mode
lib/posthog-server.ts 4/5 Singleton server-side PostHog client with immediate flush settings; follows Next.js patterns
next.config.ts 5/5 Properly configured reverse proxy rewrites and skipTrailingSlashRedirect
package.json 5/5 Both posthog-js and posthog-node added with appropriate versions
components/login.tsx 2/5 Uses email as distinct_id (should use user DB ID); email PII in capture properties; good error tracking
components/header.tsx 5/5 Correct sign-out capture and posthog.reset() call
pages/api/auth/sign-in.ts 3/5 Good server-side identify and capture with anon ID linking; email PII in capture properties
pages/api/auth/sign-up.ts 3/5 Good capture variety including invitation_accepted; email PII in capture properties
pages/api/stripe/create-checkout.ts 5/5 Clean server-side capture with relevant properties
pages/api/stripe/webhook.ts 2/5 Uses Stripe customer ID as distinct_id instead of user ID — creates orphaned identity
pages/api/team/invite.ts 3/5 Good event capture; invited_email is PII in event properties
pages/api/team/remove-member.ts 5/5 Clean capture with non-PII properties
pages/dashboard/general.tsx 4/5 Good event capture and error tracking; email in properties
pages/pricing.tsx 5/5 Clean client-side checkout_started capture with relevant properties
.gitignore 5/5 Correctly ignores .env.local
posthog-setup-report.md 4/5 Comprehensive report of changes; not a code file

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 Only adds to next.config.ts; existing configs untouched
No syntax or type errors Yes All changes are syntactically valid TypeScript
Correct imports/exports Yes posthog-js imported on client, posthog-node on server, all from correct packages
Minimal, focused changes Yes All changes relate to PostHog integration
Pre-existing issues None Base app appears functional

Issues

  • PostHog env vars not in .env.example: NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN and NEXT_PUBLIC_POSTHOG_HOST are not documented in .env.example. A .env.local file was created but gitignored. New developers won't know which env vars to set. [MEDIUM]

Other completed criteria

  • Build configuration is valid — package.json dependencies added correctly
  • No unnecessary changes — all modifications relate to PostHog
  • Code follows existing codebase patterns and conventions

PostHog implementation ⚠️

Criteria Result Description
PostHog SDKs installed Yes posthog-js@^1.363.4 and posthog-node@^5.28.5 added to package.json
PostHog client initialized Yes Client via instrumentation-client.ts (correct for Next.js 15.3+); server via singleton getPostHogClient() with flushAt: 1, flushInterval: 0
capture() Yes 10 meaningful events captured across client and server
identify() No Client uses data.email as distinct_id while server uses String(user.id) — creates split identities. Should consistently use user DB ID.
Error tracking Yes capture_exceptions: true in init config; posthog.captureException(err) in catch blocks
Reverse proxy Yes Next.js rewrites configured in next.config.ts routing /ingest/* to PostHog; api_host: '/ingest' in client init

Issues

  • Distinct ID mismatch (client vs server): posthog.identify(data.email, ...) on the client uses email as the distinct ID, but server-side handlers use String(foundUser.id) / String(createdUser.id). This creates two separate person profiles per user. The client should use the user's DB ID (returned in the API response) as the distinct ID. [CRITICAL]
  • Stripe webhook distinct_id: subscription_updated event in webhook.ts uses subscription.customer (Stripe's cus_xxx ID) as the distinct ID. This doesn't match the user's DB ID used elsewhere, creating a third orphaned identity. The handler should resolve the Stripe customer to the internal user ID. [CRITICAL]
  • Server-side host configuration: posthog-server.ts uses process.env.NEXT_PUBLIC_POSTHOG_HOST for the server host, but instrumentation-client.ts uses process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN. These are consistent env var names but the server host env var may be undefined if not set (the reverse proxy /ingest is only for client-side). The server should point directly to https://us.i.posthog.com. This works if the env var is set correctly, but there's a risk of misconfiguration. [LOW]

Other completed criteria

  • API key loaded from environment variable (NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN)
  • Host correctly configured via reverse proxy (/ingest) on client; env var on server
  • posthog.reset() called on sign-out
  • Client-to-server identity linking attempted via X-POSTHOG-DISTINCT-ID header and ``
  • capture_exceptions: true enabled in init for automatic exception capture

PostHog insights and events ⚠️

Filename PostHog events Description
components/login.tsx user_signed_in, user_signed_up, captureException Client-side auth events and error tracking on login/signup
components/header.tsx user_signed_out Captures sign-out with posthog.reset() to unlink identity
pages/api/auth/sign-in.ts user_signed_in Server-side sign-in with identify and anon ID linking
pages/api/auth/sign-up.ts user_signed_up, invitation_accepted Server-side signup with conditional invitation tracking
pages/api/stripe/create-checkout.ts checkout_session_created Server-side Stripe checkout initiation
pages/api/stripe/webhook.ts subscription_updated Subscription lifecycle tracking from Stripe webhooks
pages/api/team/invite.ts team_member_invited Team invitation tracking
pages/api/team/remove-member.ts team_member_removed Team member removal tracking
pages/dashboard/general.tsx account_updated, captureException Account settings update with error tracking
pages/pricing.tsx checkout_started Client-side checkout funnel entry point

Issues

  • PII in event properties: email is included as an event property in user_signed_in, user_signed_up, invitation_accepted, and account_updated capture calls. Email should only be in person properties via identify() or ``. The invited_email in `team_member_invited` is also PII. [MEDIUM]
  • Duplicate events on client and server: user_signed_in and user_signed_up are captured on both client-side (login.tsx) and server-side (sign-in.ts, sign-up.ts). This creates double-counted events. Pick one side (server is more reliable). [MEDIUM]

Other completed criteria

  • Events represent real user actions (signup, signin, checkout, team management)
  • Events enable product insights — full signup-to-checkout funnel is trackable
  • Events include relevant properties (team_id, price_id, role, status, etc.)
  • Event names are descriptive and consistently use snake_case

Reviewed by wizard workbench PR evaluator

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