Fix slow page loads: parallelize homepage fetches, restore caching, fix senators N+1 - #209
Merged
Conversation
…ix senators N+1 The homepage awaited getCarousel/getEvents/getFinanceHearings/getNews sequentially instead of concurrently. Several admin-editable pages had caching fully disabled (cache: "no-store" in fetchAPI, no revalidate window) so every visit re-fetched from the backend live. And the senators list endpoint issued 2 extra DB queries per senator instead of eager-loading committee memberships. - frontend/src/app/page.tsx, RecentNews.tsx: fetch homepage data via Promise.allSettled instead of sequential awaits; RecentNews is now a presentational component fed data as a prop. - frontend/src/lib/api.ts: fetchAPI takes an optional revalidateSeconds param; getStaff/getLeadership/getCommittees/getCommitteeById/ getFinanceHearings now use a 60s revalidate window instead of no-store, restoring ISR caching between admin edits. - backend/app/routers/senators.py, models/Senator.py: eager-load committee memberships via selectinload instead of querying per senator. Also fixes Senator.committee_memberships being typed as bare Mapped[list] (no type param), which made SQLAlchemy infer uselist=False and silently return a single row/None instead of a list. Closes #205
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.
Users reported the site sometimes takes a while to load. Investigation (issue #205) found three separate, compounding causes — a sequential fetch waterfall on the homepage, caching disabled on several admin-editable pages, and an N+1 query pattern on the senators API.
Changes:
frontend/src/app/page.tsx,RecentNews.tsx— the homepage awaitedgetCarousel(),getEvents(),getFinanceHearings(), and (via the nestedRecentNewsserver component)getNews()sequentially, meaning every visit stacked 4 backend round trips in series. Switched toPromise.allSettledso they run concurrently, and turnedRecentNewsinto a plain presentational component that receives its data as a prop instead of fetching on its own.frontend/src/lib/api.ts—fetchAPIhardcodedcache: "no-store", so admin-editable pages (staff, leadership, committees, finance hearings) re-fetched from the backend on every single request with zero caching. Added an optionalrevalidateSecondsparam and switchedgetStaff,getLeadership,getCommittees,getCommitteeById, andgetFinanceHearingsto a 60s revalidate window — admin edits still show up within a minute, but requests in between are served from cache instead of hitting the backend live.backend/app/routers/senators.py,models/Senator.py—list_senatorsissued 2 extra DB queries per senator (2N + 1total) instead of eager-loading committee memberships, unlikecommittees.py's equivalent endpoint which already usesselectinloadcorrectly. Fixed by eager-loading viaselectinloadin_base_query. This also surfaced a real bug in the model:Senator.committee_membershipswas typed as bareMapped[list](no type param), which made SQLAlchemy inferuselist=Falseand silently return a single row orNoneinstead of a list — exactly the landmine the old code's docstring said it was working around by hand-querying instead of using the relationship. Fixed the annotation toMapped[list["CommitteeMembership"]].Verified: all 588 backend tests pass,
tsc --noEmitis clean, andnext buildconfirms/about/staff,/committees,/funding/apply,/senators/leadershipflipped from fully dynamic (ƒ) to ISR (○, 1m revalidate).Closes #205