Skip to content

fix(personas): land next@15.5.18 + fix mixpanel SSR load_prop regression (#7327)#7399

Merged
mdmohsin7 merged 5 commits into
mainfrom
rex/personas-next-15.5-mixpanel-fix
May 20, 2026
Merged

fix(personas): land next@15.5.18 + fix mixpanel SSR load_prop regression (#7327)#7399
mdmohsin7 merged 5 commits into
mainfrom
rex/personas-next-15.5-mixpanel-fix

Conversation

@mdmohsin7
Copy link
Copy Markdown
Member

Part of #7327. Lands the previously-deferred personas next security bump by root-causing and fixing the mixpanel regression that blocked #7386 (where option 2 reverted next to 15.0.3).

Root cause (the #7386 blocker)

src/lib/mixpanel.ts called mixpanel.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, no window/localStorage). Next 15.0.3 tolerated it; Next 15.5+ evaluates it strictly and it throws Cannot 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 Mixpanel API is unchanged, so page.tsx / chat/page.tsx need zero edits. (Verified: baseline server logs Mixpanel loaded successfully server-side at 15.0.3 — exactly the unsafe path now removed.)

Security bumps (clears all 27 personas alerts)

package from to notes
next 15.0.3 15.5.18 ~25 alerts incl. 2 critical + 8 high. 15.5.18 (not .16) is first-patched for GHSA-26hh-7cqf-hhc6 "Incomplete Fix Follow-Up"
eslint-config-next ^14.2.6 ^15.5.16 Next-owned companion, coupled to the next line
glob (transitive) 10.5.0 high
postcss ^8 ^8.5.10 + overrides: { "postcss": "$postcss" } dedupes Next's vendored vulnerable postcss@8.4.31 copy too

mixpanel-browser intentionally unchanged at 2.56.0 — the bug was our usage, not the library. npm audit → 0 vulnerabilities.

Config cleanup (coupled)

Removed output: 'standalone' + a dead import next from next.config.mjs. The Dockerfile deploys via next start and never uses .next/standalone, so standalone was 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.ts regenerated by next build (version-coupled, kept in sync).

Validation (rigorous, not assumed)

  • npm audit → found 0 vulnerabilities (all 27 alerts: next/postcss/glob).
  • Build: clean at 15.5.18; / and /chat prerender with no load_prop.
  • Runtime regression-isolation: built + next start for both this branch and unmodified main (next 15.0.3), identical dummy env, Playwright walkthrough of /, /chat, /login, /u/<user>, /u/<missing>:
metric baseline (15.0.3, unmodified) this PR (15.5.18 + fix)
all 5 routes 200 200
page errors 0 0
5xx 0 0
console errors 1 (/chat 404, env/dummy-data) 1 (/chat 404 — identical)

Byte-for-byte identical error profile → zero regressions; the load_prop crash 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.

mdmohsin7 added 5 commits May 19, 2026 18:52
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-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 19, 2026

Greptile Summary

This PR upgrades next from 15.0.3 to 15.5.18 (clearing 27 vulnerability alerts including 2 critical) and fixes the SSR crash that previously blocked the upgrade by converting mixpanel.ts from eager module-level initialization to lazy, client-only initialization guarded by typeof window === 'undefined'.

  • SSR fix: mixpanel.init() is now deferred to first client-side use via ensureInit(); both identify and track early-return on the server, preventing the load_prop crash in Next 15.5+.
  • Security bumps: next → 15.5.18, eslint-config-next → ^15.5.16, postcss → ^8.5.10 with an overrides entry to deduplicate Next's vendored copy; npm audit reports 0 vulnerabilities after these changes.
  • Config cleanup: Removes the dead output: 'standalone' and unused import next from 'next' from next.config.mjs, aligning config with the actual next start deployment path.

Confidence Score: 4/5

Safe 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 typeof window === 'undefined', ensureInit() is idempotent via the initialized flag, and the loaded-callback/initialized ordering is safe because mixpanel-browser queues calls in that window. The only minor ambiguity is that the ordering (flag set before loaded fires) could surprise a future maintainer who isn't familiar with the library's internal queuing — a comment would make the intent clear. Everything else (dependency bumps, config cleanup) is mechanical and low-risk.

No files require special attention; mixpanel.ts is the only behavioral change and it reads cleanly.

Important Files Changed

Filename Overview
web/personas-open-source/src/lib/mixpanel.ts Lazy-init pattern correctly guards all mixpanel calls with typeof window === 'undefined'; one subtle point is that initialized is set to true synchronously after mixpanel.init() returns, before the loaded callback fires, but mixpanel-browser queues subsequent calls so this is safe.
web/personas-open-source/next.config.mjs Removes dead output: 'standalone' and the erroneous import next from 'next'; resulting config is minimal and correct for next start deployments.
web/personas-open-source/package.json next bumped to 15.5.18, eslint-config-next to ^15.5.16, postcss pinned to ^8.5.10 with overrides to force the same version through Next's vendored copy; all legitimate security fixes.
web/personas-open-source/next-env.d.ts Auto-generated by next build for Next 15.5; adds routes.d.ts reference and updates the docs URL — no manual edits needed.

Sequence Diagram

sequenceDiagram
    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)
Loading

Reviews (1): Last reviewed commit: "chore(personas): regenerate next-env.d.t..." | Re-trigger Greptile

Comment on lines +24 to +25
initialized = true;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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;
};

@mdmohsin7 mdmohsin7 merged commit f538e33 into main May 20, 2026
2 checks passed
@mdmohsin7 mdmohsin7 deleted the rex/personas-next-15.5-mixpanel-fix branch May 20, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant