perf: make the login page cacheable, and drop a fact that never rendered - #57
Merged
Conversation
The first page every user hits was re-rendering, and re-querying, on every request -- 0.5-1.1s TTFB measured against production, against 0.23-0.39s for a dashboard redirect. Nothing on it is per-user. It is a comptroller's name, the active semester and a booking count, all readable by `anon` under RLS. It was dynamic only because it built its Supabase client from cookies(), and calling cookies() opts a route into dynamic rendering whether or not the cookies end up mattering. Reading through a cookie-free anon client instead lets the page prerender; `revalidate = 60` regenerates it at most once a minute. In the build it moves from `f /` to `o / 1m`, and locally it serves in 8-29ms from cache rather than being rebuilt per request. Reading as `anon` is also the more correct reading. "Bookings this semester" means all of them, but the cookie client counted only what that visitor's RLS allowed, so a signed-in visitor and a signed-out one saw different numbers for a figure describing the whole organisation. The "Active reservations right now" fact is removed rather than repaired. It filtered on status 'Confirmed', which exists in none of the three tables it queried -- the real vocabulary is Reserved / Tentative / Virtual / Alternate Time / Waitlisted / Unavailable / Cancelled, and 'Confirmed' appears nowhere else in the codebase. Its count was therefore always 0, and the fact is gated on `> 0`, so it has never once been displayed. It also compared Boston booking times against a UTC clock, four hours out, and rolled to the wrong day entirely after 8pm Eastern. Three queries on the entry page's critical path, for something that could not render. That halves the page's database work as a side effect: six round trips down to three, two of which are parallel. Behaviour change: the displayed fact is picked with Math.random() at render, so it now varies per revalidation window rather than per request. Two people signing in within the same minute see the same piece of trivia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
The first page every user hits was re-rendering — and re-querying — on every request.
Measured against production:
/at 0.48–1.09 s TTFB, versus 0.23–0.39 s for a dashboard redirect. It was the slowest server render in the app, and it's the one nobody gets to skip.Why it was dynamic
Nothing on the page is per-user: a comptroller's name, the active semester, and a booking count — all readable by
anonunder RLS.It was dynamic purely because it built its Supabase client from
cookies(), and callingcookies()opts a route into dynamic rendering whether or not the cookies end up mattering. Reading through a cookie-free anon client instead lets the page prerender, withrevalidate = 60regenerating it at most once a minute.In the build it moves from
ƒ /to:Served locally from the production build, warm TTFB is 8–29 ms, and the response is byte-identical across requests (12393 every time) — previously production varied per request (12586 / 12455 / 12470) because it re-rendered and reshuffled each time.
Reading as
anonis also more correct. "Bookings this semester" means all of them, but the cookie client counted only what that visitor's RLS allowed — so a signed-in visitor and a signed-out one saw different numbers for a figure meant to describe the whole organisation.The fact that never worked
"Active reservations right now" is removed rather than repaired, per your call. Two independent defects:
It filtered on
status = 'Confirmed', which exists in none of the three tables it queried. The real vocabulary isReserved / Tentative / Virtual / Alternate Time / Waitlisted / Unavailable / Cancelled, and'Confirmed'appears nowhere else in the codebase — it was a phantom, not a drift. Verified against production:one_time_room_bookingsweekly_room_occurrencestabling_sessionsSo its count was always
0, and the fact is gated on> 0— it has never once been displayed.It also compared Boston booking times against a UTC clock. At the time I checked, the page queried
22:03while its data means18:03— four hours out, and after 8pm Eastern the date rolls to tomorrow entirely, so it looks at the wrong day.Three queries, on the entry page's critical path, for something that could not render. That halves the page's database work as a side effect: six round trips down to three, two of which run in parallel.
Behaviour change
The displayed fact is picked with
Math.random()at render, so it now varies per revalidation window rather than per request. Two people signing in within the same minute see the same piece of trivia. Given it's decorative and a user sees it once per login, that seemed the right trade for the TTFB — but it is a real change, and it's reversible by moving the pick client-side if you'd rather keep per-visitor variety.Verified
Typecheck and build pass; lint is 3 warnings better than
dev. Served the production build locally and confirmed the login form renders, a fact renders, and the dead fact is gone.