Repository navigation
perf(mainweb): ship the signed-in portal's first data with the HTML - #580
Merged
Merged
Conversation
…ent with the HTML A signed-in page waited for JS, then asked /api/auth/session (twice), then sent a tRPC batch for the portal context and registrations before it could draw. The layout now reads the session and those two queries on the server and passes them to SessionProvider and a HydrationBoundary, so the session and sidebar reads need no client round trips. Visitors without a session cookie skip it. Dashboard content visible (cold, 5 runs, 1440px): median 592 ms to 391 ms, range 506..740 to 269..471.
…n the server Dashboard, club, checkin, settings and judge each drew only after hydration plus one tRPC batch. A server page now runs those reads and hands them to the unchanged client page through a HydrationBoundary, so the batch request is gone. Cold load to h1 visible (5 runs, 1440px, base vs perf, median): dashboard 446 to 266 ms, club 490 to 304, checkin 354 to 237, settings 366 to 306, judge 521 to 459. Text, redirects and theme match the base on all five at 402 and 1440 wide.
The event page showed its loading screen until hydration plus a getById round trip. The server page now resolves the edition and hands it to the client page. Cold load to h1 visible (5 runs, 1440px, signed-in hacker, base vs perf, median): 1054 ms to 399 ms (range 452..1201 to 367..440).
All seven tabs shipped with the page although one shows at a time. Six now load through next/dynamic and are fetched once the browser is idle, so a tab click still finds them ready. Route JS for /hackathons/[id] drops from 228.5 to 205.2 KB gzip (746.9 to 669.4 KB raw). The tab switch also uses history.replaceState instead of router.replace, which made the server render the page again, and now with its reads, on every click.
The portal layout and the prefetched pages now look the session up on the server. If that lookup throws (the database unreachable, say) it took every portal page down with a server error, where before the page rendered and the browser asked for the session itself. Both now treat a failed lookup like no session: the page renders as it did before this branch and fetches for itself. Checked with a production build pointed at a closed port and a session cookie: /dashboard, /club and /hackathons/[id] answer 200.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d54116d. Configure here.
Contributor
Author
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Contributor
Author
|
Visit the preview URL for this PR (updated for commit 6905199): https://hacklytics2027--pr-580-igcb7k7k.web.app (expires Fri, 13 Nov 2026 16:08:09 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c48ba34db61581e25fe2978355160b5eefe0e83f |
…browser agree The portal pages now render on the server, which runs in UTC on Cloud Run. They formatted dates and picked "today" in the runtime's own zone, so the server HTML showed a different day or time than the browser for any visitor outside UTC, and React logged hydration error #418 on every one of those pages. Dates are now formatted in en-US on America/New_York, the venue's zone and the repo's policy for event times, through one small helper (lib/venue-time.ts) that builds each Intl.DateTimeFormat on first use. Start-of-day tests (is the event on today, has it started, is it over) compare venue calendar days instead of local midnight. Output for a visitor in New York is unchanged. No TZ is set on the server, so unrelated server date math is untouched.
This was referenced Nov 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

36 of 42 portal pages are client components. A signed-in visitor got the shell, then the JS, then
/api/auth/session(twice), then a tRPC batch for the sidebar's portal context and registrations, then the page's own reads, and only then content. Shared JS was not the cost (each page adds 10 to 40 KB gzip on a 180 KB floor); the round trips were.Changes
lib/portal-bootstrap.server.ts) and hands them toSessionProvider session=and a react-queryHydrationBoundary. Only{user, expires}reaches the client. No session cookie means no work.ClearThemeOnLeavekept.hackathons/[id]: a thin serverpage.tsxprefetches the page's first reads (components/portal/PrefetchedPage.tsx); the old client page moved to*Page.tsxunchanged. Auth redirects and role gating stay where they were.hackathons/[id]: six tabs behindnext/dynamic(Info stays static), preloaded 1 s after mount; tab switch useshistory.replaceState, sincerouter.replacere-rendered the page and its reads on every click.lib/venue-time.ts,America/New_York, en-US). These pages now render on the server, which is UTC on App Hosting, so dates printed or compared in the runtime's zone differed between server HTML and browser (Bugbot). Display and "is it today" checks in the prefetched trees, their tabs andlib/hackathon-next-stepnow use the venue's calendar, matching/hacklyticsand the judging code. A visitor in New York sees the same text as before; others now see venue dates, and non-US locales get en-US formatting. NoTZis set on the server.proxy.tsprivate no-store caching, schema, auth config.Measurements
Local production build on the repo's Docker test DB (
pnpm local:up), signed in, Playwright time until the page's h1 is visible, 5 alternating runs, median. Busy machine.Settings and judge cold are within noise. Mobile Lighthouse with the session cookie: club LCP 4536 to 2901 ms;
hackathons/[id]LCP 5036 to 4526 ms and TBT 939 to 568 ms; dashboard within noise.hackathons/[id]initial route JS 228.5 to 205.2 KB gzip; other routes +0.8 KB forHydrationBoundary.Testing
TZ=UTC, browsers in New York, LA, Kolkata and Auckland, seed dates straddling UTC midnight. React [pull] dev from main #418 mismatches across dashboard, club and everyhackathons/[id]tab: about 10 per zone before, 0 after; New York text identical before and after.venue-timeunit tests; next-step tests pass underTZAuckland, New York and UTC.tsrs --noEmit, oxlint, vitest (355 tests).Not tested
Real Neon latency (server reads now run inside the RSC request), QR scan and check-in mutations beyond render, judge/admin sub-pages,
hackathons/[id]as a signed-in non-member.Note
Medium Risk
Touches Next.js layout/session bootstrapping, React Query hydration, and timezone-sensitive hackathon logic across many portal routes, so regressions would mainly show up as auth/session or date-gating bugs rather than isolated UI issues.
Overview
Moves portal routes toward server-first rendering by splitting big client
page.tsxfiles into*Page.tsxcomponents wrapped by a newPrefetchedPagehelper that runs initialtrpcreads on the server and hydrates them into React Query.Extends the portal layout to asynchronously bootstrap auth: it creates a cached
tRPCcaller from cookies/headers, prefetches sidebar-related queries, dehydrates them, and passes a minimalSessionplus dehydrated state intoProviders, which now wiresSessionProviderand nestedHydrationBoundaryaround the tree.Refines hackathon detail UX and loading by extracting
HackathonDetailPage, lazily loading heavy tabs withnext/dynamic, preloading chunk bundles after mount, switching tab navigation tohistory.replaceStateto avoid full rerenders, and tightening registration polling using venue-day logic.Introduces
venue-timeutilities (venueFormat,venueParts,venueDayNumber) so dates and “is this the event day?” checks consistently use the Atlanta venue timezone across portal UI,hackathon-next-step, schedule/teams tabs, membership views, etc., with new Vitest coverage.Risk: medium overall — changes touch session/bootstrap plumbing, query hydration, and timezone-sensitive logic on many high-traffic portal pages, though failures fall back to the previous client-fetch behavior where possible.
Reviewed by Cursor Bugbot for commit 6905199. Bugbot is set up for automated code reviews on this repo. Configure here.