Skip to content

perf(mainweb): ship the signed-in portal's first data with the HTML - #580

Merged
aamoghS merged 6 commits into
devfrom
perf/mainweb-portal-bundles
Nov 6, 2026
Merged

aamoghS merged 6 commits into
devfrom
perf/mainweb-portal-bundles

Conversation

@github-actions

@github-actions github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

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

  • Portal layout reads the session and the sidebar's two queries on the server (lib/portal-bootstrap.server.ts) and hands them to SessionProvider session= and a react-query HydrationBoundary. Only {user, expires} reaches the client. No session cookie means no work. ClearThemeOnLeave kept.
  • Dashboard, club, checkin, settings, judge, hackathons/[id]: a thin server page.tsx prefetches the page's first reads (components/portal/PrefetchedPage.tsx); the old client page moved to *Page.tsx unchanged. Auth redirects and role gating stay where they were.
  • hackathons/[id]: six tabs behind next/dynamic (Info stays static), preloaded 1 s after mount; tab switch uses history.replaceState, since router.replace re-rendered the page and its reads on every click.
  • A failed server lookup (DB unreachable) falls back to the old behaviour instead of a server error.
  • Dates on the venue calendar (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 and lib/hackathon-next-step now use the venue's calendar, matching /hacklytics and 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. No TZ is set on the server.
  • Unchanged: proxy.ts private 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.

Page (1440 px) Base cold Perf cold Base warm Perf warm
dashboard 446 ms 266 ms 208 ms 133 ms
club 490 ms 304 ms 204 ms 164 ms
checkin 354 ms 237 ms 134 ms 117 ms
settings 366 ms 306 ms 162 ms 143 ms
judge 521 ms 459 ms 297 ms 146 ms
hackathons/[id] 1054 ms 399 ms 304 ms 196 ms

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 for HydrationBoundary.

Testing

  • Playwright at 402 and 1440, base vs perf: identical page text, theme class, final URL, no console/page/hydration errors for member (dashboard, club, checkin, settings, judge, hackathons), hacker (event page and every tab), admin (dashboard redirect to /admin), signed out and invalid token (redirect to /login with callbackUrl).
  • Hydration: production build with 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 every hackathons/[id] tab: about 10 per zone before, 0 after; New York text identical before and after. venue-time unit tests; next-step tests pass under TZ Auckland, New York and UTC.
  • Production build with an unreachable DB and a session cookie: /dashboard, /club, /hackathons/[id] answer 200.
  • 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.tsx files into *Page.tsx components wrapped by a new PrefetchedPage helper that runs initial trpc reads on the server and hydrates them into React Query.

Extends the portal layout to asynchronously bootstrap auth: it creates a cached tRPC caller from cookies/headers, prefetches sidebar-related queries, dehydrates them, and passes a minimal Session plus dehydrated state into Providers, which now wires SessionProvider and nested HydrationBoundary around the tree.

Refines hackathon detail UX and loading by extracting HackathonDetailPage, lazily loading heavy tabs with next/dynamic, preloading chunk bundles after mount, switching tab navigation to history.replaceState to avoid full rerenders, and tightening registration polling using venue-day logic.

Introduces venue-time utilities (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.

…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.
@github-actions
github-actions Bot requested a review from aamoghS as a code owner November 6, 2026 15:29
@aamoghS aamoghS changed the title Feature: perf/mainweb-portal-bundles to dev perf(mainweb): ship the signed-in portal's first data with the HTML Nov 6, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

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

@aamoghS aamoghS closed this Nov 6, 2026
@aamoghS aamoghS reopened this Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
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.
@aamoghS
aamoghS merged commit 5c1a73b into dev Nov 6, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant