perf: memoize list-row components, chunk vendor bundles, dedupe map builds#242
Merged
Conversation
…uilds Addresses the P1 findings from #219: TaskItem, ArchiveItem, and PlannedTaskCard now wrapped in React.memo so an unrelated update in one row doesn't re-render every other row in the list — this only pays off now that #237 made the context's mutation handlers stable via useCallback. Archive.tsx's handleEdit was still a fresh closure per render, which would have defeated ArchiveItem's new memo, so it's wrapped in useCallback too. vite.config.ts now has build.rollupOptions.output.manualChunks splitting recharts, @supabase/supabase-js, @radix-ui, motion, and the react-markdown dependency chain into their own vendor chunks. Drops the main bundle from ~1MB to ~296KB and clears the "chunk larger than 500kB" build warning entirely. Index.tsx's billable/non-billable hours calculation was calling getBillableHoursForDay/getNonBillableHoursForDay per archived day in two separate reduce() passes, each rebuilding projectMap/categoryMap from scratch — now a single pass using getDayStats with maps built once via useMemo. Skipped: extracting+memoizing ClientManagement.tsx's inline client rows. Unlike TaskItem/ArchiveItem/PlannedTaskCard, ClientManagement renders its list inline (no existing row component) and isn't in a hot re-render loop like the dashboard/kanban board — the refactor cost doesn't match the payoff here.
Deploying timetrackerpro with
|
| Latest commit: |
ff92fe5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://087bea7c.timetrackerpro.pages.dev |
| Branch Preview URL: | https://perf-219-p1-memo-chunking-ma.timetrackerpro.pages.dev |
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.
Summary
Addresses the 3 remaining P1 items from #219 (the 4th, useCallback wrapping, was already resolved as a side effect of #237). P2 items (PWA icon size, dead Google Fonts cache rule) are not covered here.
Type of Change
Related Issue
Addresses P1 items in #219 (not closing — issue still tracks P2 items)
Changes Made
TaskItem.tsx,ArchiveItem.tsx,PlannedTaskCard.tsxwrapped inReact.memo. This only pays off now because perf: memoize TimeTrackingContext provider, isolate 30s timer tick #237 made every context mutation handler stable viauseCallback— before that, these components would have re-rendered every time regardless ofmemosince their callback props changed identity every render.Archive.tsx:handleEditwas still a fresh closure every render, which would have silently defeatedArchiveItem's new memo (prop identity would change every time regardless ofday). Wrapped inuseCallback.vite.config.ts: addedbuild.rollupOptions.output.manualChunkssplittingrecharts,@supabase/supabase-js,@radix-ui/*,motion, and thereact-markdowndependency chain (remark/rehype/mdast/hast/micromark/unified/vfile) into their own vendor chunks. Main bundle drops from ~1MB to ~296KB; the "chunk larger than 500kB" build warning is gone entirely.Index.tsx: billable/non-billable hours were computed via two separatearchivedDays.reduce()passes callinggetBillableHoursForDay/getNonBillableHoursForDay, each rebuildingprojectMap/categoryMapfrom scratch per day. Replaced with a single pass usinggetDayStats(which already accepts pre-built maps) with the maps built once viauseMemo.Checklist
Documentation
General
mainpnpm lintandpnpm build(did not runpnpm test— not requested this session)Notes for Reviewers
Skipped deliberately: extracting+memoizing
ClientManagement.tsx's inline client rows (also named in the original audit). Unlike the other three,ClientManagementrenders its list inline with no existing row component to wrap, and it's a settings page, not a hot re-render loop like the dashboard/kanban board that the timer tick or task mutations hit constantly. The refactor cost (extract a new component, wire up stable callbacks) doesn't match the payoff there — flagging in case you disagree.Bonus finding, not fixed here: while investigating why no
vendor-rechartschunk showed up in the build output, found thatsrc/components/ui/chart.tsx(the recharts wrapper) has zero consumers anywhere insrc/— recharts was never actually reachable from the app, so it wasn't being bundled at all regardless of chunking config. Filed as a separate follow-up rather than scope-creeping this PR.Manually verified in a running dev server: started a day, added two tasks (one active, one completed) to confirm
TaskItem's memo doesn't break per-row state or delete; visited the Kanban board to confirmPlannedTaskCardrenders correctly; visited Archive and clicked Edit on the first of two entries to confirmArchiveItem's memo + thehandleEditfix still route the click to the correct day.pnpm lintandpnpm buildboth clean, build output confirms the vendor chunk split and the disappearance of the 500kB warning.