Skip to content

โšก Web Performance

๐Ÿ“‹ Overview

This page outlines the performance optimizations and architectural decisions made to achieve smooth 60 FPS rendering for large-scale visualli visualizations (5,000-10,000 nodes).

Repo: visualli.ai/backend/web/*

๐ŸŽฏ Core Web Vitals Targets

  • LCP (Largest Contentful Paint): < 2.5s
  • FID (First Input Delay): < 100ms
  • CLS (Cumulative Layout Shift): < 0.1
  • TBT (Total Blocking Time): < 400ms
  • FPS (Frames Per Second): 60 FPS sustained, > 30 FPS minimum

๐Ÿ—๏ธ Architectural Decisions

1. ๐Ÿ‘ท Web Worker for Data Processing

Decision: Offload JSONL parsing and layout calculations to a dedicated Web Worker.

Implementation:
- visualliParser.worker.ts handles all CPU-intensive operations off the main thread
- Parses .visualli JSONL format (meta + extensions + layers)
- Pre-calculates flat node layouts for ALL layers (70-400ms saved per navigation)
- Resolves node overlaps using iterative force-directed algorithm
- Returns serialized document with pre-calculated positions

Impact:
- LCP improved by 40-60%
- TBT reduced by eliminating main thread blocking during parsing
- Instant layer navigation (no recalculation needed)

2. ๐ŸŒฒ RBush Spatial Indexing

Decision: Use RBush R-tree for O(log n) viewport culling instead of O(n) filtering.

Implementation:
- RBushSpatialIndex class wraps RBush library
- Bulk-loads nodes for optimized tree construction (< 50ms for 10K nodes)
- Viewport queries complete in < 5ms
- Max entries per node: 16 (tuned from default 9)

Impact:
- Viewport queries: O(n) โ†’ O(log n)
- Pan/zoom performance: 60 FPS sustained
- Memory overhead: ~15% vs linear search, but worth the query speed

3. โ™ป๏ธ Konva Shape Pooling

Decision: Reuse Konva shape objects instead of creating/destroying on viewport changes.

Implementation:
- ShapePoolManager maintains pools of reusable node/edge shapes
- Shapes marked active/inactive instead of destroyed
- Automatic cleanup when inactive pool exceeds threshold
- Pool sizes: 50 initial, 1000 max, 150 cleanup threshold

Impact:
- Garbage collection pressure reduced by 70%
- Memory stable over 60-minute sessions (< 20% growth)
- Smoother panning/zooming (no GC pauses)

4. ๐Ÿš€ Lazy Loading & Code Splitting

Decision: Defer non-critical component loading to improve initial load metrics.

Implementation:

  • Sidebar Component:

    • Lazy loaded using React.lazy()
    • Rendered only after user interaction or idle callback
    • Suspense fallback: Skeleton placeholder for smooth transition
  • Canvas Component:

    • Deferred initialization via requestIdleCallback API
    • 500ms timeout fallback for browsers without idle callback support
    • Prevents blocking main thread during initial paint
    • Konva stage initialization delayed until DOM is interactive
  • ProcessingStatus Component:

    • Lazy loaded with dedicated Suspense boundary
    • Only loaded when file processing begins
    • Isolated error boundary to prevent cascade failures
  • Route-Based Splitting:

    • React.lazy() + Suspense for all feature routes
    • Each route bundle loaded on-demand (Settings, Projects, etc.)
    • Shared components extracted to separate chunk via Vite's manualChunks

Bundle Optimization:
- Initial bundle: 120 KB (gzipped) vs 200 KB before splitting
- Vendor chunk separation: React, Konva, and utilities in separate bundles
- Vite dynamic imports with prefetch hints for predicted user paths

Impact:
- Initial bundle size reduced by 40% (200 KB โ†’ 120 KB gzipped)
- LCP improved by 35% by prioritizing critical UI shell
- Speed Index optimized (shell UI renders in < 1.2s)
- Time to Interactive (TTI) reduced by 500ms
- Subsequent navigation instant due to route preloading

5. ๐Ÿ” Viewport Culling & Progressive Rendering

Decision: Only render nodes visible in viewport + 10% margin.

Implementation:
- Spatial index query with VIEWPORT_MARGIN = 0.1 padding
- Max 800 visible nodes enforced
- Progressive rendering in batches (75 nodes per batch) when > 30 nodes
- 5ms budget for culling queries

Impact:
- Rendering cost reduced by 90% for large graphs
- Stable 60 FPS even with 10K total nodes
- Batch rendering prevents UI freeze (< 18ms per frame)

6. ๐Ÿ’พ Memory Management

Decision: Active monitoring and cleanup to prevent memory leaks.

Implementation:
- memoryMonitor tracks heap usage and triggers warnings
- Shape pool cleanup at 150 inactive shapes
- Spatial index reset on layer navigation
- Target: 500MB, Warning: 600MB, Critical: 800MB

Impact:
- Memory growth < 20% over 60 minutes
- No memory leaks in production
- Automatic quality degradation if memory critical

๐Ÿ“Š Performance Constants

โšก FPS Targets

  • Target FPS: 60
  • Warning Threshold: 45 FPS
  • Critical Threshold: 30 FPS (triggers quality downgrade)

โฑ๏ธ Timing Budgets (milliseconds)

  • Viewport Query: < 5ms
  • Spatial Index Build: < 50ms (10K nodes)
  • Frame Time: < 16.67ms (p95 < 18ms)
  • Drag Response: < 16ms

๐Ÿง  Memory Limits

  • Target: 500 MB
  • Warning: 600 MB
  • Critical: 800 MB

๐Ÿ“ File Structure

frontend/web/src/
โ”œโ”€โ”€ core/
โ”‚   โ”œโ”€โ”€ performanceConstants.ts       # Central performance config
โ”‚   โ””โ”€โ”€ renderConfig.ts               # Quality levels & feature flags
โ”œโ”€โ”€ core-lib/
โ”‚   โ”œโ”€โ”€ workers/
โ”‚   โ”‚   โ””โ”€โ”€ visualliParser.worker.ts  # Web Worker for parsing
โ”‚   โ””โ”€โ”€ utils/
โ”‚       โ”œโ”€โ”€ spatialIndex.ts           # RBush wrapper
โ”‚       โ”œโ”€โ”€ shapePool.ts              # Konva shape pooling
โ”‚       โ””โ”€โ”€ memoryMonitor.ts          # Memory tracking
โ””โ”€โ”€ features/canvas/
    โ”œโ”€โ”€ hooks/
    โ”‚   โ””โ”€โ”€ useViewportNodes.ts       # Viewport culling hook
    โ””โ”€โ”€ components/
        โ””โ”€โ”€ VisualliMindMapCanvas.enhanced.tsx

Note: For complete repository structure, see FOLDER_STRUCTURE.md.

โœจ Key Non-Negotiables For All Future Changes

  1. Web Workers are critical - Offload all heavy computation from main thread
  2. Spatial indexing is non-negotiable - RBush makes large graphs feasible
  3. Object pooling prevents GC pauses - Reuse shapes instead of recreating
  4. Lazy loading improves UX - Prioritize critical UI, defer the rest
  5. Viewport culling is mandatory - Never render what users can't see

๐Ÿ”ฎ Future Optimizations

๐Ÿšง Planned Improvements

  1. WebGL Renderer: For > 15K nodes (PixiJS integration)
  2. Virtual Scrolling: Infinite canvas with chunk loading
  3. Level-of-Detail (LOD): Simplified rendering at high zoom-out
  4. IndexedDB Caching: Persist parsed documents locally

๐Ÿ“ˆ Performance Monitoring

  • Continuous FPS tracking with warnings
  • Memory usage alerts
  • Automatic quality adjustment based on device capabilities