-
Notifications
You must be signed in to change notification settings - Fork 0
⚡ Bolt: Optimize project detail page data fetching #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ## 2026-04-27 - [Concurrent & Lazy Data Fetching in Detail Pages] | ||
| **Learning:** Sequential network requests blocking the initial render path create noticeable UI latency, particularly in detail pages that need both a single entity and a list of related entities (e.g., project details + lists within the project). Further, fetching secondary data that is only needed after specific user interactions (e.g., fetching *all* lists just in case the user wants to add a list) compounds this delay unnecessarily. | ||
| **Action:** When a page needs multiple independent pieces of data, always dispatch the network requests concurrently using `Promise.all()`. For secondary data, defer fetching until the user interacts with the feature requiring it (lazy fetching), keeping the initial load fast and lean. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import re | ||
|
|
||
| with open("src/app/projects/[id]/page.tsx", "r") as f: | ||
| content = f.read() | ||
|
|
||
| if "fetchProjectAndLists" not in content: | ||
| print("Function not found") | ||
| else: | ||
| print("Function found") | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -38,11 +38,48 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st | |||||||||||||||||||
| const [editName, setEditName] = useState(""); | ||||||||||||||||||||
| const [editDescription, setEditDescription] = useState(""); | ||||||||||||||||||||
| const [showAddList, setShowAddList] = useState(false); | ||||||||||||||||||||
| const [hasFetchedAllLists, setHasFetchedAllLists] = useState(false); | ||||||||||||||||||||
| const [newListName, setNewListName] = useState(""); | ||||||||||||||||||||
| const [isCreating, setIsCreating] = useState(false); | ||||||||||||||||||||
| const [isShareDialogOpen, setIsShareDialogOpen] = useState(false); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||
| const fetchProjectAndLists = async () => { | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| setIsLoading(true); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // ⚡ Bolt: Fetch project details and project lists concurrently | ||||||||||||||||||||
| const [projectResponse, listsResponse] = await Promise.all([ | ||||||||||||||||||||
| fetch(`/api/projects/${projectId}`), | ||||||||||||||||||||
| fetch(`/api/projects/${projectId}/lists`), | ||||||||||||||||||||
| ]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // ⚡ Bolt: Parse JSON concurrently | ||||||||||||||||||||
| const [projectResult, listsResult] = await Promise.all([ | ||||||||||||||||||||
| projectResponse.json(), | ||||||||||||||||||||
| listsResponse.json(), | ||||||||||||||||||||
| ]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (!projectResult.success) { | ||||||||||||||||||||
| setError(projectResult.error || "Project not found"); | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| setProject(projectResult.data); | ||||||||||||||||||||
| setEditName(projectResult.data.name); | ||||||||||||||||||||
| setEditDescription(projectResult.data.description || ""); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (listsResult.success) { | ||||||||||||||||||||
| setLists(listsResult.data); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||
| console.error("Error fetching project:", err); | ||||||||||||||||||||
| setError("Failed to load project"); | ||||||||||||||||||||
| } finally { | ||||||||||||||||||||
| setIsLoading(false); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (isLoaded && !isSignedIn) { | ||||||||||||||||||||
| router.push(`/sign-in?redirect_url=/projects/${projectId}`); | ||||||||||||||||||||
| return; | ||||||||||||||||||||
|
|
@@ -53,43 +90,24 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st | |||||||||||||||||||
| } | ||||||||||||||||||||
| }, [isLoaded, isSignedIn, projectId, router]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const fetchProjectAndLists = async () => { | ||||||||||||||||||||
| const fetchAllUserLists = async () => { | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| setIsLoading(true); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Fetch project details | ||||||||||||||||||||
| const projectResponse = await fetch(`/api/projects/${projectId}`); | ||||||||||||||||||||
| const projectResult = await projectResponse.json(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (!projectResult.success) { | ||||||||||||||||||||
| setError(projectResult.error || "Project not found"); | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| setProject(projectResult.data); | ||||||||||||||||||||
| setEditName(projectResult.data.name); | ||||||||||||||||||||
| setEditDescription(projectResult.data.description || ""); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Fetch lists in project | ||||||||||||||||||||
| const listsResponse = await fetch(`/api/projects/${projectId}/lists`); | ||||||||||||||||||||
| const listsResult = await listsResponse.json(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (listsResult.success) { | ||||||||||||||||||||
| setLists(listsResult.data); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Fetch all user lists (for adding to project) | ||||||||||||||||||||
| const allListsResponse = await fetch("/api/lists"); | ||||||||||||||||||||
| const allListsResult = await allListsResponse.json(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (allListsResult.success) { | ||||||||||||||||||||
| setAllLists(allListsResult.data); | ||||||||||||||||||||
| setHasFetchedAllLists(true); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||
| console.error("Error fetching project:", err); | ||||||||||||||||||||
| setError("Failed to load project"); | ||||||||||||||||||||
| } finally { | ||||||||||||||||||||
| setIsLoading(false); | ||||||||||||||||||||
| console.error("Error fetching all user lists:", err); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
99
to
+105
|
||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const handleToggleAddList = () => { | ||||||||||||||||||||
| setShowAddList(!showAddList); | ||||||||||||||||||||
| if (!showAddList && !hasFetchedAllLists) { | ||||||||||||||||||||
| fetchAllUserLists(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -321,7 +339,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st | |||||||||||||||||||
| <WikiButton onClick={() => setIsShareDialogOpen(true)}> | ||||||||||||||||||||
| Share | ||||||||||||||||||||
| </WikiButton> | ||||||||||||||||||||
| <WikiButton variant="primary" onClick={() => setShowAddList(!showAddList)}> | ||||||||||||||||||||
| <WikiButton variant="primary" onClick={handleToggleAddList}> | ||||||||||||||||||||
| {showAddList ? "Cancel" : "Add List"} | ||||||||||||||||||||
| </WikiButton> | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
|
|
@@ -400,7 +418,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st | |||||||||||||||||||
| <p className="text-wiki-text-muted mb-4"> | ||||||||||||||||||||
| This project has no lists yet. | ||||||||||||||||||||
| </p> | ||||||||||||||||||||
| <WikiButton variant="primary" onClick={() => setShowAddList(true)}> | ||||||||||||||||||||
| <WikiButton variant="primary" onClick={() => { setShowAddList(true); if (!hasFetchedAllLists) fetchAllUserLists(); }}> | ||||||||||||||||||||
|
||||||||||||||||||||
| <WikiButton variant="primary" onClick={() => { setShowAddList(true); if (!hasFetchedAllLists) fetchAllUserLists(); }}> | |
| <WikiButton | |
| variant="primary" | |
| onClick={() => { | |
| if (!showAddList) { | |
| handleToggleAddList(); | |
| } | |
| }} | |
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Python script appears to be a one-off verification helper (it just checks for a substring in a TSX file) and introduces an otherwise-unused Python artifact into a Node/Next.js repo (there are no npm scripts or other Python utilities). Recommend removing it from the PR, or moving it under
scripts/and rewriting it in TS/JS (or wiring it into CI) if it’s intended to be kept. Also,reis imported but unused.