Skip to content

fix(search-explore): eliminate per-card remixes-count N+1 in featured contests#14412

Open
dylanjeffers wants to merge 1 commit into
mainfrom
fix/explore-contest-card-count-n-plus-1
Open

fix(search-explore): eliminate per-card remixes-count N+1 in featured contests#14412
dylanjeffers wants to merge 1 commit into
mainfrom
fix/explore-contest-card-count-n-plus-1

Conversation

@dylanjeffers
Copy link
Copy Markdown
Contributor

Problem

Loading the new search-explore page fires ~20 `GET /v1/tracks/:id/remixes?limit=0&offset=0&only_contest_entries=true&only_cosigns=false` requests — one per ContestCard in the Featured Remix Contests carousel.

Root cause

`FeaturedRemixContestsSection` calls `useExploreContent`, which reads a static `explore-content.json` listing featured contest track IDs with no entry-count data. Each `` then independently issues a count-only request via `useRemixesCount({ trackId, isContestEntry: true })` (`packages/web/src/components/contest-card/ContestCard.tsx:255`). N cards = N requests.

The cache-priming side-effect that other contest surfaces rely on already exists in `packages/common/src/api/tan-query/events/useAllRemixContests.ts:93-103` (and mirrored in `useUserRemixContests.ts:100`) — it walks `related.entryCounts` and calls `queryClient.setQueryData(getRemixesCountQueryKey(...), count)` for each contest, turning ContestCard's count fetch into a cache hit. But `useExploreContent` doesn't return entry counts, so this surface never benefited.

Fix

Piggy-back on `useAllRemixContests`'s built-in prime by calling it side-effect-only in the section, gated on the same `inView` as `useExploreContent`. One batched request seeds the remixes-count cache for the top-30 active contests; ContestCards in the featured carousel then hit cache instead of firing their own count fetches.

```tsx
useAllRemixContests({ pageSize: PRIME_BATCH_SIZE }, { enabled: inView })
```

The return value is intentionally unused — we only care about the priming inside the hook's queryFn.

Why not a backend fix

The cleanest solution would be to extend `explore-content.json` (or a new endpoint) to include entry counts per featured contest. That requires a static-content rebuild + a hook signature change, which is out of scope for this fix. The client-side prime above resolves the immediate N+1 with one extra request total, deferring the backend cleanup.

Test plan

  • Open the new search-explore page; observe the network panel. Featured Remix Contests cards should NOT each fire `/v1/tracks/:id/remixes?only_contest_entries=true`. One `/v1/events/remix-contests?limit=30` request should be visible instead.
  • Confirm the entry-count badge on each ContestCard still renders correctly.
  • Confirm no regressions on `/contests` (also uses `useAllRemixContests`) — the additional consumer doesn't double-fetch because TanStack Query dedupes by query key.

🤖 Generated with Claude Code

… contests

Rendering the new search-explore page fired ~20 GET
/v1/tracks/:id/remixes?limit=0&only_contest_entries=true requests —
one per ContestCard in the Featured Remix Contests carousel.

Root cause: `useExploreContent` (the hook backing the carousel) reads
a static `explore-content.json` listing featured contest track IDs
with no entry-count data, so each ContestCard's
`useRemixesCount({trackId, isContestEntry: true})` independently
fires a count-only request. The cache-priming side-effect that other
contest surfaces rely on
(`packages/common/src/api/tan-query/events/useAllRemixContests.ts:93-103`,
also mirrored in `useUserRemixContests`) wasn't reachable from this
surface.

Fix: piggy-back on `useAllRemixContests`'s built-in prime by calling
it side-effect-only in `FeaturedRemixContestsSection`, gated on the
same `inView` as the main fetch. One batched request seeds the
remixes-count cache for the top-N active contests; ContestCards then
hit cache instead of issuing their own count fetches. Featured
contests in the prime batch (the common case — featured is a
curated subset of active) drop from N requests to 0.

No backend or shared-hook changes; isolated to the carousel section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 27, 2026

⚠️ No Changeset found

Latest commit: 2e632d4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

🌐 Web preview ready

Preview URL: https://audius-web-preview-pr-14412.audius.workers.dev

Unique preview for this PR (deployed from this branch).
Workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant