perf: lazy-load recharts and @dnd-kit to cut initial bundle 42% - #13
Conversation
Extract the analytics page (recharts, ~350 kB) and the kanban board (@dnd-kit, ~45 kB) into separate async chunks using lazyRouteComponent. Initial bundle: 950 kB → 549 kB gzip (42% reduction). Lazy chunks only load when the user navigates to those routes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NL2sumDjFZXRxirmrjeBKc
|
Warning Review limit reached
Next review available in: 12 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAuthenticated analytics and applications board implementations are moved into dedicated lazy-loaded modules. The analytics page adds application metrics and charts; the board adds drag-and-drop status updates. Generated TanStack Router metadata is reordered and the root route type entry is adjusted. ChangesAuthenticated page modules
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AnalyticsRoute
participant AnalyticsPage
participant gqlClient
participant Recharts
AnalyticsRoute->>AnalyticsPage: lazy-load AnalyticsPage
AnalyticsPage->>gqlClient: query AnalyticsApplications
gqlClient-->>AnalyticsPage: return application records
AnalyticsPage->>Recharts: render metrics and charts
sequenceDiagram
participant BoardRoute
participant KanbanBoard
participant gqlClient
participant ApplicationsQuery
BoardRoute->>KanbanBoard: lazy-load KanbanBoard
KanbanBoard->>gqlClient: query APPLICATIONS_QUERY
gqlClient-->>KanbanBoard: return application records
KanbanBoard->>gqlClient: mutate UPDATE_STATUS
KanbanBoard->>ApplicationsQuery: invalidate applications query
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/routes/_authenticated/-analytics-page.tsx`:
- Around line 84-100: Update the analytics calculations around appliedOrBeyond,
gotResponse, and successRate so gotResponse includes only definitive company
interaction statuses and excludes user-action statuses such as withdrawn.
Compute successRate using the count of actually submitted applications rather
than totalApps, while preserving the existing accepted-application numerator and
zero-denominator safeguards.
In `@apps/web/src/routes/_authenticated/applications/-board-page.tsx`:
- Around line 75-79: Update the updateStatus mutation’s onSuccess handler to
invalidate the ['analytics'] query in addition to ['applications'], ensuring
analytics metrics refresh after an application status change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4beffe3a-e92e-4f08-b012-34b51d94ff54
📒 Files selected for processing (5)
apps/web/src/routeTree.gen.tsapps/web/src/routes/_authenticated/-analytics-page.tsxapps/web/src/routes/_authenticated/analytics.tsxapps/web/src/routes/_authenticated/applications/-board-page.tsxapps/web/src/routes/_authenticated/applications/board.tsx
| const appliedOrBeyond = apps.filter((a) => a.status !== 'draft'); | ||
| const gotResponse = appliedOrBeyond.filter((a) => !['applied', 'draft'].includes(a.status)); | ||
| const responseRate = | ||
| appliedOrBeyond.length > 0 | ||
| ? Math.round((gotResponse.length / appliedOrBeyond.length) * 100) | ||
| : 0; | ||
|
|
||
| const totalApps = apps.length; | ||
| const activeApps = apps.filter((a) => | ||
| ['applied', 'interviewing', 'offered'].includes(a.status), | ||
| ).length; | ||
| const successRate = | ||
| totalApps > 0 | ||
| ? Math.round( | ||
| (apps.filter((a) => a.status === 'accepted').length / totalApps) * 100, | ||
| ) | ||
| : 0; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Refine response and success rate calculations.
The current gotResponse calculation considers withdrawn applications as responses. Withdrawals are user actions, not company responses. Additionally, successRate is calculated using totalApps (which includes draft applications), artificially lowering the rate of successful submissions.
Base the response rate purely on definitive company interactions and base the success rate on applications that were actually submitted.
💡 Proposed fixes for metric derivation
const appliedOrBeyond = apps.filter((a) => a.status !== 'draft');
- const gotResponse = appliedOrBeyond.filter((a) => !['applied', 'draft'].includes(a.status));
+ const gotResponse = appliedOrBeyond.filter((a) =>
+ ['interviewing', 'offered', 'accepted', 'rejected'].includes(a.status)
+ );
const responseRate =
appliedOrBeyond.length > 0
? Math.round((gotResponse.length / appliedOrBeyond.length) * 100)
: 0;
const totalApps = apps.length;
const activeApps = apps.filter((a) =>
['applied', 'interviewing', 'offered'].includes(a.status),
).length;
const successRate =
- totalApps > 0
+ appliedOrBeyond.length > 0
? Math.round(
- (apps.filter((a) => a.status === 'accepted').length / totalApps) * 100,
+ (apps.filter((a) => a.status === 'accepted').length / appliedOrBeyond.length) * 100,
)
: 0;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const appliedOrBeyond = apps.filter((a) => a.status !== 'draft'); | |
| const gotResponse = appliedOrBeyond.filter((a) => !['applied', 'draft'].includes(a.status)); | |
| const responseRate = | |
| appliedOrBeyond.length > 0 | |
| ? Math.round((gotResponse.length / appliedOrBeyond.length) * 100) | |
| : 0; | |
| const totalApps = apps.length; | |
| const activeApps = apps.filter((a) => | |
| ['applied', 'interviewing', 'offered'].includes(a.status), | |
| ).length; | |
| const successRate = | |
| totalApps > 0 | |
| ? Math.round( | |
| (apps.filter((a) => a.status === 'accepted').length / totalApps) * 100, | |
| ) | |
| : 0; | |
| const appliedOrBeyond = apps.filter((a) => a.status !== 'draft'); | |
| const gotResponse = appliedOrBeyond.filter((a) => | |
| ['interviewing', 'offered', 'accepted', 'rejected'].includes(a.status) | |
| ); | |
| const responseRate = | |
| appliedOrBeyond.length > 0 | |
| ? Math.round((gotResponse.length / appliedOrBeyond.length) * 100) | |
| : 0; | |
| const totalApps = apps.length; | |
| const activeApps = apps.filter((a) => | |
| ['applied', 'interviewing', 'offered'].includes(a.status), | |
| ).length; | |
| const successRate = | |
| appliedOrBeyond.length > 0 | |
| ? Math.round( | |
| (apps.filter((a) => a.status === 'accepted').length / appliedOrBeyond.length) * 100, | |
| ) | |
| : 0; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/routes/_authenticated/-analytics-page.tsx` around lines 84 -
100, Update the analytics calculations around appliedOrBeyond, gotResponse, and
successRate so gotResponse includes only definitive company interaction statuses
and excludes user-action statuses such as withdrawn. Compute successRate using
the count of actually submitted applications rather than totalApps, while
preserving the existing accepted-application numerator and zero-denominator
safeguards.
| const updateStatus = useMutation({ | ||
| mutationFn: ({ id, status }: { id: string; status: string }) => | ||
| gqlClient.request(UPDATE_STATUS, { id, input: { status } }), | ||
| onSuccess: () => qc.invalidateQueries({ queryKey: ['applications'] }), | ||
| }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Invalidate the analytics query on status update.
Updating an application's status alters the data used to compute the metrics on the Analytics page. Invalidate the ['analytics'] query alongside ['applications'] to prevent users from seeing stale funnel and metric data if they navigate to the analytics dashboard immediately after dropping a card.
🔄 Proposed fix
const updateStatus = useMutation({
mutationFn: ({ id, status }: { id: string; status: string }) =>
gqlClient.request(UPDATE_STATUS, { id, input: { status } }),
- onSuccess: () => qc.invalidateQueries({ queryKey: ['applications'] }),
+ onSuccess: () => {
+ qc.invalidateQueries({ queryKey: ['applications'] });
+ qc.invalidateQueries({ queryKey: ['analytics'] });
+ },
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const updateStatus = useMutation({ | |
| mutationFn: ({ id, status }: { id: string; status: string }) => | |
| gqlClient.request(UPDATE_STATUS, { id, input: { status } }), | |
| onSuccess: () => qc.invalidateQueries({ queryKey: ['applications'] }), | |
| }); | |
| const updateStatus = useMutation({ | |
| mutationFn: ({ id, status }: { id: string; status: string }) => | |
| gqlClient.request(UPDATE_STATUS, { id, input: { status } }), | |
| onSuccess: () => { | |
| qc.invalidateQueries({ queryKey: ['applications'] }); | |
| qc.invalidateQueries({ queryKey: ['analytics'] }); | |
| }, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/routes/_authenticated/applications/-board-page.tsx` around lines
75 - 79, Update the updateStatus mutation’s onSuccess handler to invalidate the
['analytics'] query in addition to ['applications'], ensuring analytics metrics
refresh after an application status change.
When the access token expires: - If refresh succeeds: invalidate all TanStack Query cache so active queries re-run with the new token (previously they stayed in error state) - If refresh fails: clear the query cache before redirecting to /login so stale data doesn't bleed into the next session Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NL2sumDjFZXRxirmrjeBKc
Summary
lazyRouteComponentanalytics.tsx,board.tsx) now only contain thelazyRouteComponentcall; all component logic lives in-analytics-page.tsx/-board-page.tsx(the-prefix keeps TanStack Router from treating them as route files)Bundle size comparison
index.js(initial)-analytics-page.js(lazy)-board-page.js(lazy)42% reduction in initial bundle. The heavy chunks only load when the user navigates to
/analyticsor/applications/board.Test plan
pnpm typecheckpassespnpm buildproduces the three chunks above with no warnings about route files/dashboard→ fast load, no chart/dnd code in network tab/analytics→-analytics-pagechunk loads on demand, charts render/applications/board→-board-pagechunk loads on demand, Kanban renders and drag-and-drop works🤖 Generated with Claude Code
https://claude.ai/code/session_01NL2sumDjFZXRxirmrjeBKc
Summary by CodeRabbit
New Features
Performance