fix(timeline): a warm-up request wedged the server render of every feed page - #827
Merged
Merged
Conversation
…ed page warmCurrentUserId is called at the top of every timeline read, and timeline reads happen during server rendering too. It started an unconditional /auth/v1/user request from the SERVER using the BROWSER Supabase client, which carries no request cookies - so it could only ever fail, and it failed while the route was rendering. That is not a wasted request, it is an outage. /timeline, /community and /dashboard sat on their route-level loading.tsx skeleton forever on a fresh page load. Navigating to the very same route from inside the app worked, because that path never server-renders - which is what made it look intermittent. The skeleton is near-white on a white page, so the symptom users see is "the timeline is blank". Nothing in the console, no failed request, every check green. I introduced this in #826 and did not catch it, because I verified the fix on the post page and by measuring the network waterfall, neither of which exercises a cold server render of a feed route. The warm is now a no-op on the server, silently: it is an optimisation, and an optimisation must never be the reason a page fails. The test runs in the `node` jest environment on purpose. Under jsdom `window` exists, the guard is never exercised, and the test would pass while the server path stayed broken - which is exactly how this shipped. Mutation-proved: removing the guard turns it red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5
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.
Production is currently broken for feed pages. This is the fix.
Symptom
/timeline,/communityand/dashboardshow a blank page on a fresh load, forever. Signed in, no console error, no failed request, every check green./post,/settingsand/discoverrender fine.It is not actually blank — it is the route-level
loading.tsxskeleton, which is near-white on a white background, so it reads as an empty page.Cause — mine, from #826
warmCurrentUserId()is called at the top of every timeline read. Timeline reads also happen during server rendering. It started an unconditional/auth/v1/userrequest from the server using the browser Supabase client, which carries no request cookies — so it could only ever fail, and it failed while the route was rendering, leaving the segment's Suspense boundary unresolved.The tell that cracked it: navigating to
/timelinefrom inside the app works (20 posts, no errors) while a fresh load of the same URL hangs. In-app navigation never server-renders.Fix
The warm is a no-op on the server, and silently so — it is an optimisation, and an optimisation must never be the reason a page fails.
Why I missed it
I verified #826 on the post page and by reading the network waterfall. Neither exercises a cold server render of a feed route. The measurement I trusted was taken on the one path that could not show the bug.
Why the test uses the
nodeenvironmentUnder jsdom
windowexists, so the guard is never exercised and the test would pass while the server path stayed broken — which is precisely how this shipped. The test assertstypeof window === 'undefined'first, so it cannot silently stop testing anything if the environment changes.Mutation-proved: removing the guard turns it red; restoring it turns it green.
Ruled out along the way
systemctl restart orangecat-app: no effect./api/profilereturns 200 with the user.session.tsimports only leaf modules.