Skip to content

[CI] (3393d57) react-router/saas-template#902

Closed
wizard-ci-bot[bot] wants to merge 1 commit intomainfrom
wizard-ci-3393d57-react-router-saas-template
Closed

[CI] (3393d57) react-router/saas-template#902
wizard-ci-bot[bot] wants to merge 1 commit intomainfrom
wizard-ci-3393d57-react-router-saas-template

Conversation

@wizard-ci-bot
Copy link

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

Automated wizard CI run

Source: wizard-pr
Trigger ID: 3393d57
App: react-router/saas-template
App directory: apps/react-router/saas-template
Workbench branch: wizard-ci-3393d57-react-router-saas-template
Wizard branch: release-please--branches--main--components--wizard
Context Mill branch: main
PostHog (MCP) branch: master
Timestamp: 2026-03-17T00:42:40.429Z
Duration: 813.1s

@wizard-ci-bot
Copy link
Author

wizard-ci-bot bot commented Mar 17, 2026

Now I have all the context I need. Let me produce the evaluation.


PR Evaluation Report

Summary

This PR integrates PostHog analytics into a React Router v7 SaaS template. It adds client-side initialization with PostHogProvider, server-side middleware using posthog-node, user identification in the authenticated layout, event tracking for login/signup/org creation/contact sales/Stripe webhooks, and error tracking in the root error boundary.

Files changed Lines added Lines removed
13 +226 -19

Confidence score: 4/5 👍

  • Missing env var documentation: VITE_PUBLIC_POSTHOG_PROJECT_TOKEN and VITE_PUBLIC_POSTHOG_HOST are not added to .env.example, which will confuse new developers setting up the project. [MEDIUM]
  • Email as person property in identify: posthog.identify(currentUserId, { email: currentUserEmail }) is acceptable since email is a person property via identify(), but the currentUserEmail is also exposed through the loader data response, which is fine for authenticated routes. [LOW]
  • Error boundary captureException called during render: posthog?.captureException(error) is called directly in the component body of BaseErrorBoundary, not inside a useEffect. This means it fires on every render, including re-renders, potentially sending duplicate error reports. [MEDIUM]
  • Stripe webhook events use hardcoded distinctId: "stripe-webhook": All server-side Stripe webhook events use the same static distinctId, which creates a single phantom "user" in PostHog. This prevents linking billing events to actual users. [MEDIUM]

File changes

Filename Score Description
app/entry.client.tsx 4/5 PostHog initialization and PostHogProvider wrapper — clean implementation
app/lib/posthog-middleware.ts 4/5 New server-side middleware with context propagation via withContext()
app/root.tsx 3/5 Middleware registration and error tracking, but captureException in render body
app/routes/.../login.tsx 4/5 Login event tracking with method property
app/routes/.../register.tsx 4/5 Signup event tracking with method property
app/routes/.../new.tsx 4/5 Organization creation tracking via wrapper div onSubmit
app/routes/contact-sales.tsx 4/5 Contact sales submission tracking via wrapper div onSubmit
app/routes/.../_sidebar-layout.tsx 4/5 User identification with userId and email
app/features/billing/stripe-event-handlers.server.ts 3/5 Server-side Stripe webhook tracking with hardcoded distinctId
vite.config.ts 5/5 SSR noExternal config for posthog packages
package.json 5/5 Dependencies added correctly
.gitignore 5/5 .env added to gitignore
posthog-setup-report.md 4/5 Comprehensive setup documentation

App sanity check ⚠️

Criteria Result Description
App builds and runs Yes All syntax is valid, imports resolve, dependencies added
Preserves existing env vars & configs Yes No existing env vars or configs removed; middleware array extended properly
No syntax or type errors Yes TypeScript syntax correct across all files
Correct imports/exports Yes posthog-js, posthog-node, @posthog/react all imported correctly for their contexts
Minimal, focused changes Yes Changes are focused on PostHog integration with minor formatting adjustments
Pre-existing issues None No pre-existing issues observed

Issues

  • Missing PostHog env vars in .env.example: VITE_PUBLIC_POSTHOG_PROJECT_TOKEN and VITE_PUBLIC_POSTHOG_HOST are used throughout the app but not documented in .env.example. New developers won't know they need these variables. [MEDIUM]

Other completed criteria

  • All changes relate to PostHog integration
  • Correct files modified for this framework (entry.client for init, root for middleware/error boundary, route files for events)
  • Code follows existing codebase patterns (import ordering, naming conventions, TypeScript usage)
  • vite.config.ts SSR noExternal config is appropriate for React Router v7
  • .gitignore updated to exclude .env

PostHog implementation ⚠️

Criteria Result Description
PostHog SDKs installed Yes posthog-js@^1.360.2, posthog-node@^5.28.2, @posthog/react@^1.8.2 added to package.json
PostHog client initialized Yes posthog.init() called in entry.client.tsx with env vars and defaults: "2026-01-30"; server-side via posthog-node in middleware and webhook handlers
capture() Yes Multiple meaningful capture calls: user_login_requested, user_signup_requested, organization_created, contact_sales_submitted, plus server-side Stripe events
identify() Yes posthog.identify(currentUserId, { email: currentUserEmail }) called in authenticated sidebar layout
Error tracking Yes posthog.captureException(error) in root ErrorBoundary, though called during render instead of useEffect
Reverse proxy No No reverse proxy configured. The __add_tracing_headers option is for distributed tracing, not a reverse proxy. No Next.js rewrites or similar proxy setup present

Issues

  • No reverse proxy configured: The PR does not set up a reverse proxy to route PostHog requests through the app's domain. For a client-side app, this is recommended to prevent ad blockers from intercepting PostHog requests. The __add_tracing_headers option only adds tracing headers to fetch requests, it is not a reverse proxy. [MEDIUM]
  • captureException called during render: In BaseErrorBoundary, posthog?.captureException(error) is called directly in the component function body. This is a side effect during render, which violates React best practices and will fire on every re-render, potentially sending duplicate error reports. It should be wrapped in a useEffect. [MEDIUM]
  • Stripe webhook hardcoded distinctId: captureWebhookEvent uses distinctId: "stripe-webhook" for all events. This creates a single phantom user in PostHog instead of linking billing events to actual users. The organization ID or customer ID from the event could be used instead, or group analytics could link these to organizations. [MEDIUM]

Other completed criteria

  • API key loaded from environment variable (VITE_PUBLIC_POSTHOG_PROJECT_TOKEN)
  • API host correctly configured via VITE_PUBLIC_POSTHOG_HOST env var
  • Server-side PostHog properly configured with flushAt: 1 and flushInterval: 0 for short-lived request handlers
  • PostHogProvider wraps the entire app in entry.client.tsx
  • Server-side middleware uses withContext() to correlate sessions between client and server
  • posthog.shutdown() called after server-side event capture

PostHog insights and events ⚠️

Filename PostHog events Description
app/routes/.../login.tsx user_login_requested Captures login attempts with method: "email" or method: "google" property
app/routes/.../register.tsx user_signup_requested Captures signup attempts with method: "email" or method: "google" property
app/routes/.../new.tsx organization_created Captures organization creation form submission
app/routes/contact-sales.tsx contact_sales_submitted Captures contact sales form submission
app/features/billing/stripe-event-handlers.server.ts checkout_completed, subscription_created, subscription_cancelled Server-side Stripe webhook events with billing properties
app/root.tsx captureException Error tracking in root ErrorBoundary

Issues

  • organization_created and contact_sales_submitted have no properties: These events are captured with bare posthog.capture("event_name") with no enriching properties. Adding properties like organization name, plan type, or form context would make them more useful for analytics. [MEDIUM]

Other completed criteria

  • Events represent real user actions (login, signup, org creation, contact sales, billing events)
  • Events enable product insights (signup→org creation→checkout funnel possible)
  • No PII in event properties (email only in identify() person properties, not in capture calls)
  • Event names are descriptive and use consistent snake_case naming convention
  • Server-side Stripe events include relevant billing properties (amount, currency, subscription_id, status)

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