Peels keeps public pages fast by avoiding a server-side Supabase auth refresh unless a route actually needs auth before it can render. Public pages can still become auth-aware after hydration, but their first HTML response should be cheap, useful, and crawlable.
src/proxy.ts decides whether a request needs a fresh server auth check.
- Auth-required paths use
updateSession(). This creates a Supabase server client, callssupabase.auth.getUser(), refreshes cookies when needed, forwardsx-peels-auth-state, and applies auth redirects for protected or guest-only pages. - Public paths use
createSignedOutResponse(). This forwards the current path and a signed-out auth hint without calling Supabase, so pages such as/,/share, and static content do not block first paint on auth. authRequiredPathPrefixesshould stay small. Add to it only when the first server response truly needs auth state./mapand/listingsbelong there because they server-render auth-aware listing data before client hydration.
The forwarded auth state is a rendering hint. On public routes that do not need server auth, it intentionally says signed-out on the initial server render even if the browser has a valid session cookie. Client-side auth slots can then resolve the real state after hydration.
Public pages should use the locale cookie as the fast path. Signed-in profile-backed locale lookup belongs on authenticated/private flows where the server has already paid the auth cost.
If a signed-in user sees the wrong language on a public page, first check whether their locale cookie is stale or missing. Do not fix that by making every public request call supabase.auth.getUser().
Auth-aware UI on public pages should be isolated to small client components:
AccountButtoncan resolve the current user after hydration.FooterLocaleSlotdecides whether to show the guest language picker after client auth resolves.- Unread chat dots can appear after the scoped unread check completes.
This means the first paint can briefly look signed-out on public pages. That is expected. The page should converge to the right signed-in or signed-out UI after hydration.
UnreadMessagesProvider belongs near the UI that displays or consumes unread state, such as tab-bar and chat layouts. It should not wrap the root layout.
The unread check should stay deferred so it does not delay public HTML. If the unread dot disappears entirely, check the provider scope, the idle auth check, and the unread chat_messages count before moving the provider back to the root.
The homepage should server-render useful static content first, then hydrate dynamism later.
IntroHeaderreserves the hero visual space without server-rendering the decorative map/avatar/pin frame.DeferredIntroHeaderRotatorloads the animated hero rotator after the first paint/idle window.PeelsHowItWorkskeeps crawlable explanatory content in the initial HTML.- Deferred demo components load map, listing, chat, and photo demos after intersection or idle.
Avoid importing production-heavy interactive components such as listing reads, map demos, or chat windows directly into the initial homepage path unless the performance tradeoff is intentional.
Selected chat routes need auth, thread lists, selected-thread data, and metadata. Shared server lookups should go through the cached helpers in src/features/chat/chatPageData.ts so the layout, page, and generateMetadata() do not repeat the same Supabase work in one request.
- If guests can reach a protected page, check
authRequiredPathPrefixes,updateSession(), and the route-level redirect assumptions. - If a public page becomes slow, check that it was not added to
authRequiredPathPrefixesand that no server component on that path now calls auth,headers(), or profile lookup unnecessarily. - If footer locale behaviour looks wrong, inspect
FooterLocaleSlotanduseAuthUser(). Remember that the guest picker appears only after client auth resolves. - If signed-in preferred locale is missing on a public first render, treat that as expected unless the locale cookie should have been updated.
- If unread dots are missing, check provider scope and the deferred unread lookup before broadening the provider.
- If selected chat pages duplicate Supabase calls, route shared work through
src/features/chat/chatPageData.ts.
For auth/session, homepage, and chat changes, prefer production-style e2e checks:
npm run test:e2e:prod -- e2e/home.spec.ts e2e/chat.spec.ts e2e/i18n.spec.tsAdd e2e/seo.spec.ts when metadata, public routes, or crawlable content are affected.