Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Deploying timeline-prototype with
|
| Latest commit: |
4ad7b70
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://09e5cbba.timeline-prototype.pages.dev |
| Branch Preview URL: | https://fix-blank-first-load.timeline-prototype.pages.dev |
Opening the app showed an empty parchment rectangle until sign-in resolved, which on a cold load takes several seconds. Nothing on screen said it was working, so it read as broken, and the reliable fix was to reload — which works, because the second load has a warm token. The app was teaching people to refresh it. The loading screen had no content: the right background, the grain overlay, and nothing else. It now has a spinner and a line of text, after a 400ms pause so a fast load still shows nothing — a spinner flashed for 200ms reads as jank rather than speed. After nine seconds it says it is taking too long and offers the reload people were reaching for anyway, which is the error state PRODUCTION-TODO #10 asked for. Profile attempts also get 2.5s, 4s, then 6s rather than a flat 6s. A transient failure is likelier than a genuinely slow query, and the old first attempt cost nearly seven seconds before the retry began. What that second change is not: a fix for the cold-start wait. I first wrote it as one, claiming the initial fetch raced Supabase's token rotation. Reading auth-js 2.101.1 says otherwise — __loadSession refreshes an expired session itself, and onAuthStateChange holds the init lock until it has, so INITIAL_SESSION only ever reaches us with a valid token. The seconds go on that refresh round-trip, before any of our code runs, and nothing here can shorten it. Only make it legible, which is what this does.
DanialBeg
force-pushed
the
fix/blank-first-load
branch
from
October 1, 2026 23:39
cfbef69 to
9962c43
Compare
The claim is that the wait is auth-js swapping a refresh token for a live one before it hands us a session. That is read off its source, not measured on a real connection, and the difference matters: if it is right there is nothing to optimise here, and if it is wrong we have been looking in the wrong place. Three marks — the app starting, the auth listener firing, the profile landing — and one console line splitting the total between them. The first figure is everything before our code runs; the second is our own query, with which attempt won. Off unless asked for: any dev server, or `?timing` on the URL so a deployed preview can be checked without a build. Nothing leaves the browser.
This branch was successfully deployed
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 screenshot is the app working as written. That blank parchment rectangle is the loading screen:
App.tsxrendered the background and the grain overlay and nothing else — adisplay: flex; align-items: centercontainer with nothing to centre.So a load that was merely slow was indistinguishable from one that had died, and reloading was the rational response. It works, because the second load has a warm token. The app was teaching people to refresh it.
What this fixes
A real loading screen, with two rules worth stating:
Profile attempts also get 2.5s / 4s / 6s rather than a flat 6s, because a transient failure is likelier than a genuinely slow query and the old first attempt cost 6.6s before the retry began.
What this does not fix, and why there is nothing to fix
I first wrote the timeout change as a fix for the cold-start wait, claiming the initial fetch raced Supabase's token rotation. That was wrong, and reading the client says so plainly.
In
auth-js 2.101.1,__loadSessionrefreshes an expired session itself before returning it:and
onAuthStateChangewaits for initialization and the lock before emitting:So
INITIAL_SESSIONonly ever reaches our callback with a valid token.getProfilenever races the rotation, andfetchProfileis not where a cold load spends its time.The seconds go on the refresh round-trip itself — swapping a refresh token for a new access token — which happens before any of our code runs, on a first visit after the access token expired (1h by default) and not on an immediate reload. That matches the reported symptom exactly, and nothing in this repo can shorten it: an authenticated query cannot go out before the token it needs exists.
What we can do is stop showing a blank page during it. That is this PR.
Honest summary
~3s of "Loading your dashboard…" instead of ~7s of nothing. The blank page is gone. The cold-start delay is a required network call and is unchanged.
If you want certainty about the split between that refresh and everything else, the next step is measuring it on a real cold load with a real session, which I cannot do from here. Happy to add the instrumentation if the number matters.
Testing
533 passing, lint and typecheck clean, build succeeds. Four new cases on the timing, since that is what silently regresses: nothing before 400ms, text after, the stuck state with its reload at 9s, and
role="status"/aria-liveso it is not silence to a screen reader either.(Force-pushed once to correct the commit message, which asserted the same wrong mechanism. No one else was on the branch.)