Bug Description
In src/pages/CodingVerse.jsx (around line 370), the leaderboard effect fetches every completed user from Firestore and then sorts and slices client-side:
const q = query(
collection(db, "users"),
where("onboardingStatus", "==", "complete")
// no orderBy, no limit
);
const snapshot = await getDocs(q);
const sortedUsers = users
.filter(u => (u.points?.codingVersePoints || 0) > 0)
.sort((a, b) => b.points.codingVersePoints - a.points.codingVersePoints)
.slice(0, 20); // only 20 are ever shown
This downloads every user document on every tab switch (the effect re-runs each time activeSidebarTab becomes "leaderboard"). Firestore charges per document read — at 1,000 users that's 1,000 reads for a list that only ever shows 20 entries. The client is also doing work the database should be doing.
Why it can't just be a limit(50) patch
Adding limit without orderBy("points.codingVersePoints", "desc") returns an arbitrary 50 users, not the top 50. And adding that orderBy without a matching composite index in firestore.indexes.json will throw a Firestore index error at runtime.
Proposed fix
- Add a composite index to
firestore.indexes.json:
{
"collectionGroup": "users",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "onboardingStatus", "order": "ASCENDING" },
{ "fieldPath": "points.codingVersePoints", "order": "DESCENDING" }
]
}
- Replace the unbounded query:
const q = query(
collection(db, "users"),
where("onboardingStatus", "==", "complete"),
where("points.codingVersePoints", ">", 0),
orderBy("points.codingVersePoints", "desc"),
limit(20)
);
const snapshot = await getDocs(q);
const sortedUsers = snapshot.docs.map(d => ({ uid: d.id, ...d.data() }));
- Add a simple timestamp guard (similar to the
syncGitHubData cooldown pattern already in the codebase) so repeat tab switches within a few minutes don't re-fetch.
Bug Description
In
src/pages/CodingVerse.jsx(around line 370), the leaderboard effect fetches every completed user from Firestore and then sorts and slices client-side:This downloads every user document on every tab switch (the effect re-runs each time
activeSidebarTabbecomes "leaderboard"). Firestore charges per document read — at 1,000 users that's 1,000 reads for a list that only ever shows 20 entries. The client is also doing work the database should be doing.Why it can't just be a
limit(50)patchAdding
limitwithoutorderBy("points.codingVersePoints", "desc")returns an arbitrary 50 users, not the top 50. And adding thatorderBywithout a matching composite index infirestore.indexes.jsonwill throw a Firestore index error at runtime.Proposed fix
firestore.indexes.json:{ "collectionGroup": "users", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "onboardingStatus", "order": "ASCENDING" }, { "fieldPath": "points.codingVersePoints", "order": "DESCENDING" } ] }syncGitHubDatacooldown pattern already in the codebase) so repeat tab switches within a few minutes don't re-fetch.