fix(personas): land next@15.5.18 + fix mixpanel SSR load_prop regression (#7327)#7399
Conversation
mixpanel.init() ran at module scope, so it executed during App-Router SSR (no window/localStorage). Next 15.0.3 tolerated it; Next 15.5+ surfaces it as 'Cannot read properties of undefined (reading load_prop)' on / and /chat (the regression that blocked #7386). Initialize lazily on first client-side use; no-op on the server. Public Mixpanel API unchanged - no consumer edits needed.
…7327) Dockerfile deploys via 'next start' and never uses .next/standalone, so output:'standalone' was dead config emitting a 'next start does not work with output: standalone' warning. Align config with the actual deploy path; also drop an unused 'import next'.
…tcss floor (#7327) Clears ~25 next Dependabot alerts (2 critical + 8 high + med/low; 15.5.18 is first-patched for the Incomplete-Fix-Follow-Up advisory). eslint-config-next coupled to 15.5.x; postcss ^8.5.10 floor + overrides {postcss:$postcss} to also dedupe Next's vendored copy.
npm audit -> 0 vulnerabilities. next 15.5.18, glob 10.5.0, postcss 8.5.15 (single deduped copy), eslint-config-next 15.5.18, mixpanel-browser unchanged 2.56.0.
Auto-generated by 'next build' for Next 15.5; kept in sync with the bumped Next version.
Greptile SummaryThis PR upgrades
Confidence Score: 4/5Safe to merge — the lazy-init SSR fix is correctly implemented, the security bumps are appropriate, and the config cleanup removes genuinely dead options. The mixpanel change is the only non-trivial code change. The lazy initialization pattern is standard and correct: both exported methods guard with No files require special attention; Important Files Changed
Sequence DiagramsequenceDiagram
participant SSR as Next.js SSR (Node)
participant Client as Browser (Client)
participant MP as mixpanel.ts module
participant SDK as mixpanel-browser
Note over SSR,SDK: Before this PR — eager init at module scope
SSR->>MP: import mixpanel.ts
MP->>SDK: mixpanel.init() crash: load_prop undefined
Note over SSR,SDK: After this PR — lazy init with SSR guard
SSR->>MP: import mixpanel.ts
MP-->>SSR: "initialized = false, no init called"
Client->>MP: Mixpanel.track("event")
MP->>MP: "typeof window === 'undefined'? No"
MP->>MP: "ensureInit() — initialized=false, call mixpanel.init()"
MP->>SDK: mixpanel.init(token, options)
SDK-->>MP: loaded callback fires async
MP->>MP: "initialized = true"
MP->>SDK: mixpanel.track("event", props)
Client->>MP: Mixpanel.track("another")
MP->>MP: "ensureInit() — initialized=true, skip"
MP->>SDK: mixpanel.track("another", props)
Reviews (1): Last reviewed commit: "chore(personas): regenerate next-env.d.t..." | Re-trigger Greptile |
| initialized = true; | ||
| }; |
There was a problem hiding this comment.
The
initialized flag is set to true unconditionally after calling mixpanel.init(). If mixpanel.init() throws (e.g. due to a missing or malformed NEXT_PUBLIC_MIXPANEL_TOKEN), the thrown exception escapes ensureInit() and initialized correctly stays false, so the next call retries — that part is fine. The concern is the inverse: if init succeeds but the SDK isn't yet "ready" (the loaded callback is asynchronous), a track() call immediately after ensureInit() will call into the SDK before the loaded callback fires. mixpanel-browser is designed to queue events in this window so no data is lost, but it's worth making the ordering explicit with a comment so future maintainers don't assume loaded has fired by the time initialized is true.
| initialized = true; | |
| }; | |
| // initialized=true is set synchronously so subsequent calls skip re-init. | |
| // mixpanel-browser queues track/identify calls until the 'loaded' callback | |
| // fires, so setting this flag before 'loaded' is intentional and safe. | |
| initialized = true; | |
| }; |
Part of #7327. Lands the previously-deferred personas
nextsecurity bump by root-causing and fixing the mixpanel regression that blocked #7386 (where option 2 revertednextto 15.0.3).Root cause (the #7386 blocker)
src/lib/mixpanel.tscalledmixpanel.init()at module top-level. App-Router'use client'pages are still server-rendered on the initial request, so this module executed during SSR (Node, nowindow/localStorage). Next 15.0.3 tolerated it; Next 15.5+ evaluates it strictly and it throwsCannot read properties of undefined (reading 'load_prop')on/and/chat.Fix: initialize lazily on first client-side use and no-op on the server. The exported
MixpanelAPI is unchanged, sopage.tsx/chat/page.tsxneed zero edits. (Verified: baseline server logsMixpanel loaded successfullyserver-side at 15.0.3 — exactly the unsafe path now removed.)Security bumps (clears all 27 personas alerts)
overrides: { "postcss": "$postcss" }postcss@8.4.31copy toomixpanel-browserintentionally unchanged at 2.56.0 — the bug was our usage, not the library.npm audit→ 0 vulnerabilities.Config cleanup (coupled)
Removed
output: 'standalone'+ a deadimport nextfromnext.config.mjs. The Dockerfile deploys vianext startand never uses.next/standalone, sostandalonewas dead config that emits a"next start does not work with output: standalone"warning (present even at 15.0.3). Aligns config with the real deploy path.next-env.d.tsregenerated bynext build(version-coupled, kept in sync).Validation (rigorous, not assumed)
npm audit→ found 0 vulnerabilities (all 27 alerts: next/postcss/glob)./and/chatprerender with noload_prop.next startfor both this branch and unmodified main (next 15.0.3), identical dummy env, Playwright walkthrough of/,/chat,/login,/u/<user>,/u/<missing>:/chat404, env/dummy-data)/chat404 — identical)Byte-for-byte identical error profile → zero regressions; the
load_propcrash is gone.Alerts cleared
27 (web/personas-open-source/package-lock.json): ~25 next (2 critical + 8 high + med/low) + 1 postcss + 1 glob.
Closes the deferred residual from #7386.