diff --git a/.jules/bolt.md b/.jules/bolt.md index 43fafef..b562ba9 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -5,3 +5,7 @@ ## 2024-05-18 - Concurrent Fetching with Owner IDs in Share Links **Learning:** In the share system, endpoints processing a share code typically suffer from waterfall latency by first loading target entity details (like a project) and then querying its associated elements (like project lists) using the entity's owner ID (`userId`). However, the `shareLink` object already contains the `userId` field (representing the owner's ID). **Action:** Always leverage the existing owner's ID within `shareLink` to bypass sequential dependencies and fetch parent entities (like projects) concurrently with their child elements (like user lists) using `Promise.all`. + +## 2024-07-06 - In-Memory Derivation of Data Subsets +**Learning:** When fetching both a global collection (e.g., all lists for a user) and a subset of that collection (e.g., lists associated with a specific project) simultaneously on the frontend, making two separate API requests causes redundant database queries and network latency. +**Action:** Always derive the subset in-memory using array filtering (e.g., `allLists.filter(list => list.projectId === projectId)`) from the globally fetched collection, eliminating the redundant subset API request entirely. diff --git a/src/app/projects/[id]/page.tsx b/src/app/projects/[id]/page.tsx index 8e121a4..5c64088 100644 --- a/src/app/projects/[id]/page.tsx +++ b/src/app/projects/[id]/page.tsx @@ -57,18 +57,16 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st try { setIsLoading(true); - // OPTIMIZATION: Execute independent network requests and JSON parsing concurrently - // using Promise.all. This prevents a 3-step waterfall, reducing Time to First Byte - // (TTFB) and overall load time significantly on this detail page. - const [projectRes, listsRes, allListsRes] = await Promise.all([ + // OPTIMIZATION: Instead of making separate network requests for a subset of lists + // (project lists) and the global collection (all lists), we only fetch the global + // collection. We then derive the project lists in-memory, eliminating a redundant API request. + const [projectRes, allListsRes] = await Promise.all([ fetch(`/api/projects/${projectId}`), - fetch(`/api/projects/${projectId}/lists`), fetch("/api/lists"), ]); - const [projectResult, listsResult, allListsResult] = await Promise.all([ + const [projectResult, allListsResult] = await Promise.all([ projectRes.json(), - listsRes.json(), allListsRes.json(), ]); @@ -81,12 +79,13 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st setEditName(projectResult.data.name); setEditDescription(projectResult.data.description || ""); - if (listsResult.success) { - setLists(listsResult.data); - } - if (allListsResult.success) { - setAllLists(allListsResult.data); + const allFetchedLists = allListsResult.data as List[]; + setAllLists(allFetchedLists); + + // Derive the subset of lists that belong to this project + const projectLists = allFetchedLists.filter((list) => list.projectId === projectId); + setLists(projectLists); } } catch (err) { console.error("Error fetching project:", err);