Skip to content

fix: resolve pricing page React 19 hydration mismatch (toLocaleString + authMode) #SUPERLOG - #468

Open
superlog-app[bot] wants to merge 1 commit into
mainfrom
superlog/fix-pricing-hydration-mismatch
Open

fix: resolve pricing page React 19 hydration mismatch (toLocaleString + authMode) #SUPERLOG#468
superlog-app[bot] wants to merge 1 commit into
mainfrom
superlog/fix-pricing-hydration-mismatch

Conversation

@superlog-app

@superlog-app superlog-app Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

The /pricing page was throwing React 19 hydration error #418 (text node mismatch) every time a user visited from a browser whose locale differs from the Node.js build environment. During prerendering, Node.js in a POSIX/C locale formats 2_000_000 as "2000000" (no commas); the user's browser (en-US) formats it as "2,000,000". React 19 detects the text node mismatch during hydrateRoot and discards the prerendered tree, causing a visible layout shift and slower page load.

A secondary bug exists in the authMode useState lazy initializer, which reads window.location.hash during render. On the server (prerender) typeof window === "undefined" is true so it returns null; on the client, if the URL hash contains sso-callback or verify (e.g. after SSO redirect), it returns "sign-in". Both SSR and client-hydration render must agree on the same initial state.

Root cause

In PaygEstimator, three ScaleSlider instances called n.toLocaleString() without a locale argument. toLocaleString() is locale-sensitive: its output is determined by the runtime's locale, which differs between the Node.js build container (typically POSIX/C) and end-user browsers (e.g. en-US). This is documented by React as a common cause of hydration mismatches.

The authMode initializer used a typeof window === "undefined" guard, which is the canonical React SSR/CSR mismatch anti-pattern.

Changes

  • apps/web/src/Pricing.tsx: pin toLocaleString("en-US") in all three slider formatValue callbacks so both SSR and browser produce identical number strings.
  • apps/web/src/Pricing.tsx: change authMode from a lazy initializer that reads window.location.hash to a plain null initial state, and move the hash check into useEffect so SSR and initial hydration render agree.

Incident: eager-iguana (199724da-2620-43b1-b27c-8b171964016d)


Was this PR helpful? Leave feedback — goes straight to the Superlog team.


Summary by cubic

Fixes React 19 hydration mismatch on /pricing by pinning number formatting to a stable locale and deferring URL-hash reads. Previously SSR rendered 2000000 and the browser rendered 2,000,000, causing React to discard the prerendered tree and a layout shift; now both sides agree and authMode initializes consistently.

  • Pin toLocaleString("en-US") for all three ScaleSlider formatValue callbacks in apps/web/src/Pricing.tsx to make SSR and client output identical. Numbers now always display with en-US separators.
  • Initialize authMode to null and move window.location.hash parsing for sso-callback/verify into useEffect, so hydration matches SSR and sign-in opens after mount when applicable.

Written for commit 52eeba1. Summary will update on new commits.

Review in cubic

… + authMode) #SUPERLOG

Delivery-Id: 40395550a8491087bc7e3df0c53d2cd4040aaaa54d64fe6b323044ef6f4c3c87
Delivery-Base: main
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