Server-render the authenticated app, and fix the fallback org that 500s

Performancekamo-internal
Shipped
August 26, 2026 at 3:39 AM UTC
Author
kamo
Commit
dba4d98

MemberListProvider returned null until its mount effect ran, and it sits above NavTop AND {children} — so that one line kept the ENTIRE authenticated application out of the server-rendered HTML. The document was ~1.23 MB carrying almost no markup: nothing painted until the root shell had downloaded, parsed and hydrated, and PageSpeed reported no LCP element because there was nothing to measure. Verified by building and serving the app, not by reasoning about it. Against the built standalone server, / now returns 200 with 16,033 bytes of real DOM across 152 elements — two headers, ten buttons, "My Businesses / My Profile / Log out" — where /validate, on which AuthedChrome deliberately renders children bare, returns 1,622 bytes across 16. That difference IS the shell, and it is now in the HTML. Safe because both values NavTop receives are deterministic — members and openNodes both start as {} — so the server's output and the first client render agree by construction. Nothing under here reads a browser global while rendering; scripts/check-ssr-safe.mjs proves it and runs in npm test. Note what this does NOT change: LoadingProvider still starts isLoading true, so the brand splash covers the shell for its 500 ms. The nav is now behind that splash rather than absent, which is the prerequisite for the splash decision being worth making at all. The same exercise turned up a real bug sitting behind the gate. React cannot serialize a class instance from a server component into a client one, and the layout's FALLBACK organisation is built with Organization.fromJSON — a real instance. The happy path never hit it because fetchOrganization returns `await res.json()`, already plain and merely cast. So the fallback that exists to keep the app usable when the organisation fetch fails was instead taking the whole document down with a 500. I hit it directly: with the backend unreachable, / returned 500 "Only plain objects... can be passed to Client Components". Fixed by spreading into a plain object at the boundary, which covers both paths. OrgProvider already re-hydrates whatever it is handed — `organization instanceof Organization ? organization : **************** — so a plain object is exactly what it expects, and is what production has always sent it. The second, inline fallback at the SessionProvider call was unreachable (the block above it already guarantees a non-null organisation) and is gone. Verified: production build succeeds; / and /validate both return 200 from the built server; tsc --noEmit clean; all 10 guards pass.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing