Back to all docs

Navigation Performance & UX Issues

Analysis of navigation performance between HomePage ↔ ChatPage ↔ Library (DocsPage) ↔ Doc Reading (DocViewPage) for mobile PWA and desktop webapp.


Critical Performance Issues 🔴

1. No Code Splitting / Lazy Loading

Location: src/App.tsx:1-38

All routes load eagerly in one 458KB bundle. Every page component (ChatPage, DocsPage, DocViewPage, ContactsPage) loads upfront even if the user never visits them.

Impact:

Fix: Implement lazy loading:

const ChatPage = lazy(() => import('@/pages/ChatPage'))
const DocsPage = lazy(() => import('@/pages/DocsPage'))
const DocViewPage = lazy(() => import('@/pages/DocViewPage'))

2. Massive lucide-react Bundle (44MB node_modules!)

Location: Throughout codebase

You're importing the entire lucide-react library. While tree-shaking helps, this is still wasteful.

Current usage:

import { ChevronLeft, Search, Plus, MoreVertical, BookOpen, Send } from 'lucide-react'

Impact:

Better approach: Use lucide-static or switch to a lighter icon library.


3. Context Value Recreation Causes Unnecessary Re-renders

Location:

Context values are recreated on every render, causing all consumers to re-render:

// DocsContext.tsx:135
const value: DocsContextValue = {
  docs,              // ← Changes when docs update
  loading,           // ← Changes when loading state changes
  error,             // ← Changes when error state changes
  fetchDocs,         // ← Stable (useCallback)
  getDoc,            // ← Stable (useCallback)
  recentDocs,        // ← Stable (useMemo)
  repos,             // ← Changes when repos update
  collections,       // ← Changes when collections update
  getDocsByRepo,     // ← UNSTABLE! Recreated on repos change
  // ... 6 more functions
}

Impact:

Fix: Memoize the context value:

const value = useMemo(() => ({
  docs, loading, error, fetchDocs, getDoc, recentDocs,
  repos, collections, getDocsByRepo, getDocsByCollection,
  createCollection, deleteCollection, addDocToCollection, removeDocFromCollection,
}), [docs, loading, error, fetchDocs, getDoc, recentDocs, repos, collections, ...])

4. DocsPage Fetches on Every Mount

Location: src/pages/DocsPage.tsx:14-16

useEffect(() => {
  fetchDocs()
}, [fetchDocs])

Problem:

Impact:

Fix: Add data staleness check:

const { docs, loading, error, fetchDocs, lastFetched } = useDocs()

useEffect(() => {
  const FIVE_MINUTES = 5 * 60 * 1000
  if (!docs.length || Date.now() - lastFetched > FIVE_MINUTES) {
    fetchDocs()
  }
}, [fetchDocs, docs.length, lastFetched])

5. DocCard Gradient Recalculated on Every Render

Location: src/components/DocCard.tsx:12

const gradient = generateGradient(doc.title)

Called on every render for every doc card. With 50 docs visible, that's 50 gradient calculations on each render.

Fix: Memoize it:

const gradient = useMemo(() => generateGradient(doc.title), [doc.title])

6. react-markdown Loaded Eagerly

Location: src/pages/DocViewPage.tsx:6

import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

Problem: Markdown libraries are loaded even if user never opens a doc.

Fix: Lazy load the DocViewPage component (see #1).


7. CollectionSection N+1 Problem (Already Identified)

Location: src/pages/DocsPage.tsx:119-131

Each collection makes a separate HTTP request sequentially.

Impact:

Solution:


UX Sluggishness Issues 🟡

8. No Transition/Loading State Between Routes

Location: src/App.tsx

When navigating between pages, there's no loading indicator. The page just freezes until the next route loads.

Impact:

Fix: Add Suspense boundaries:

<Suspense fallback={<PageLoader />}>
  <Routes>
    <Route path="/" element={<ContactsPage />} />
    ...
  </Routes>
</Suspense>

9. No Back/Forward Navigation Optimization ⚠️ NEEDS INVESTIGATION

Location: React Router default behavior

React Router doesn't preserve scroll position or component state when navigating back.

Impact:

Fix: Add scroll restoration with ScrollRestoration component

UPDATE: ScrollRestoration component causes app to crash with blank white page in react-router-dom v7.9.6. Need to investigate alternative solutions:

Status: REMOVED from Phase 1 - requires more investigation


10. Messages localStorage Thrashing

Location: src/contexts/ChatContext.tsx:112-120

useEffect(() => {
  saveMessageHistory(messagesByRepo)  // Writes to localStorage on EVERY message
}, [messagesByRepo])

Problem:

Impact:

Fix: Debounce the saves:

useEffect(() => {
  const timeout = setTimeout(() => {
    saveMessageHistory(messagesByRepo)
  }, 500)
  return () => clearTimeout(timeout)
}, [messagesByRepo])

Performance Summary Table

Issue Impact Priority Difficulty Estimated Time
1. No code splitting High Critical Easy 30min
2. lucide-react size Medium Medium Medium 1-2hr
3. Context re-renders High Critical Medium 1hr
4. DocsPage refetches Medium High Easy 20min
5. Gradient recalc Low-Medium Medium Easy 5min
6. react-markdown eager load Medium Medium Easy 10min (covered by #1)
7. N+1 collections High High Hard (backend) 2-4hr
8. No transition states Medium (UX) Medium Easy 30min
9. No scroll restoration Medium (UX) Medium Medium POSTPONED - needs investigation
10. localStorage thrashing Medium Medium Easy 15min

Total estimated time for all fixes: 6-9 hours


Quick Wins (< 30 minutes each)

These should be tackled first for immediate performance gains:

  1. ⏳ Add code splitting for routes (30min) → Reduces initial bundle by ~60%
  2. ✅ Memoize DocCard gradient (5min) → Eliminates unnecessary CPU work
  3. ✅ Add staleness check to DocsPage (20min) → Eliminates redundant API calls
  4. ✅ Debounce localStorage writes (15min) → Reduces main thread blocking
  5. ❌ Add scroll restoration (POSTPONED) → Needs investigation - ScrollRestoration breaks app
  6. ⏳ Add Suspense boundaries (30min) → Gives visual feedback during navigation

Total completed: 3/6 fixes (~40 minutes) Status: Phase 1 partially complete - scroll restoration requires custom implementation


Medium Priority (1-2 hours each)

  1. Memoize all context values (1hr)
  2. Evaluate lucide-react alternatives (1-2hr)

Long-term / Backend Changes

  1. Fix N+1 collections query (2-4hr, requires backend changes)

Testing Checklist

After implementing fixes, test these scenarios:

Mobile PWA

Desktop

Metrics to Track


Implementation Order

Suggested order to maximize impact while minimizing risk:

  1. Phase 1 - Quick Wins (Day 1)

    • Issue #5: Memoize DocCard gradient
    • Issue #10: Debounce localStorage writes
    • Issue #9: Add scroll restoration
    • Issue #4: Add staleness check to DocsPage
  2. Phase 2 - Code Splitting (Day 1-2)

    • Issue #1: Implement lazy loading for all routes
    • Issue #8: Add Suspense boundaries
    • Issue #6: Verify react-markdown is code-split
  3. Phase 3 - Context Optimization (Day 2)

    • Issue #3: Memoize all context values
    • Test for regression in re-render behavior
  4. Phase 4 - Dependencies (Day 3)

    • Issue #2: Evaluate lucide-react alternatives
    • If switching libraries, update all icon imports
  5. Phase 5 - Backend (Day 4+)

    • Issue #7: Fix N+1 collections query
    • Coordinate with backend team

Bundle Analysis

Current state:

dist/assets/index-DTTrcyQm.js: 458KB

Target after fixes:

Total download for full app: ~550KB → acceptable for PWA Initial load: ~330KB → 28% reduction


Additional Notes