Skip to content

Latest commit

 

History

History
281 lines (230 loc) · 7.45 KB

File metadata and controls

281 lines (230 loc) · 7.45 KB

Performance Optimizations for AI Model Comparison App

Overview

This document outlines the comprehensive performance optimizations implemented to reduce loading times and improve user experience, particularly when switching between sections (Price Comparison → Pricing Calculator → Versus Comparison).

Key Performance Issues Identified

1. Large Data Bundle (13,276 lines, ~500KB)

  • Problem: The entire aiModels.ts file was loaded on initial page load
  • Impact: Slow initial load and large JavaScript bundle

2. Inefficient Component Re-renders

  • Problem: Components re-rendered unnecessarily on state changes
  • Impact: 2-second delays when switching between sections

3. No Data Caching or Lazy Loading

  • Problem: Data was processed on every render
  • Impact: Repeated expensive computations

Optimizations Implemented

1. Lazy Loading & Code Splitting

Dynamic Imports

// Before: Direct imports
import ModelComparison from './components/ModelComparison'

// After: Dynamic imports with loading states
const ModelComparison = dynamic(() => import('./components/ModelComparison'), {
  loading: () => <LoadingSpinner />,
  ssr: false
})

Component-Level Lazy Loading

const PricingCalculator = lazy(() => import('./PricingCalculator'));
const VersusComparison = lazy(() => import('./VersusComparison'));
const OptimizedModelTable = lazy(() => import('./OptimizedModelTable'));

2. Data Service with Caching

Intelligent Caching System

// Cache for storing loaded data
const dataCache = new Map<string, any>();
const CACHE_EXPIRY = 5 * 60 * 1000; // 5 minutes

// Lazy loading function for model data
export const loadModelData = async (mode: AIModelMode): Promise<any[]> => {
  const cacheKey = `models_${mode}`;
  const cached = dataCache.get(cacheKey) as CacheEntry;
  
  // Check if we have valid cached data
  if (cached && Date.now() - cached.timestamp < CACHE_EXPIRY) {
    return cached.data;
  }
  
  // Dynamic import based on mode to reduce initial bundle size
  const { aiModels } = await import('./aiModels');
  // ... cache the data
}

Filtered Data Caching

// Optimized filtering with memoization
const filterCache = new Map<string, any[]>();

export const getFilteredModels = async (
  mode: AIModelMode,
  searchTerm: string,
  selectedProviders: string[] = []
): Promise<any[]> => {
  const cacheKey = `filtered_${mode}_${searchTerm}_${selectedProviders.join(',')}`;
  
  // Check cache first
  if (filterCache.has(cacheKey)) {
    return filterCache.get(cacheKey)!;
  }
  // ... filter and cache results
}

3. React Performance Optimizations

React.memo for Component Memoization

// Memoized table row component
const ModelRow = React.memo(({ model, showPricingCalculator, ... }) => {
  // Component logic
});

// Memoized main components
export default React.memo(OptimizedModelTable);
export default React.memo(ModelComparison);

useCallback for Event Handlers

// Memoized handlers to prevent unnecessary re-renders
const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
  setSearchTerm(e.target.value);
}, []);

const handleModeChange = useCallback((mode: AIModelMode) => {
  setSelectedMode(mode);
}, []);

useMemo for Expensive Computations

// Memoized component props to prevent unnecessary re-renders
const tableProps = useMemo(() => ({
  selectedMode,
  setSelectedMode: handleModeChange,
  searchTerm: debouncedSearchTerm,
  showPricingCalculator,
  // ... other props
}), [
  selectedMode,
  handleModeChange,
  debouncedSearchTerm,
  // ... dependencies
]);

4. Debounced Search

Custom Hook for Debouncing

export const useDebounce = <T>(value: T, delay: number): T => {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
};

Implementation

// Debounce search term to prevent excessive filtering
const debouncedSearchTerm = useDebounce(searchTerm, 300);

5. Preloading Strategy

Background Data Preloading

// Preload other modes for faster switching
useEffect(() => {
  availableModes.forEach(mode => {
    if (mode !== selectedMode) {
      preloadModeData(mode as AIModelMode);
    }
  });
}, [selectedMode]);

6. Bundle Optimization

Next.js Configuration

// Performance optimizations
experimental: {
  optimizeCss: true,
  optimizePackageImports: ['lucide-react', '@radix-ui/react-icons'],
},

// Webpack optimizations
webpack: (config, { dev, isServer }) => {
  // Split chunks for better caching
  if (!dev && !isServer) {
    config.optimization.splitChunks = {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          enforce: true,
        },
      },
    };
  }
  
  return config;
},

Bundle Analysis

{
  "scripts": {
    "analyze": "ANALYZE=true next build",
    "analyze:server": "BUNDLE_ANALYZE=server next build",
    "analyze:browser": "BUNDLE_ANALYZE=browser next build"
  }
}

7. Loading States & UX Improvements

Suspense Boundaries

<Suspense fallback={<LoadingSpinner />}>
  {showVersusComparison ? (
    <VersusComparison {...versusProps} />
  ) : (
    <OptimizedModelTable {...tableProps} />
  )}
</Suspense>

Search Loading Indicator

{searchTerm !== debouncedSearchTerm && (
  <div className="absolute right-3 top-1/2 transform -translate-y-1/2">
    <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400"></div>
  </div>
)}

Performance Monitoring

Development Tools

  • Performance Monitor Component: Press Ctrl+Shift+P to view metrics
  • Bundle Analyzer: Run npm run analyze to analyze bundle sizes
  • Custom Hooks: usePerformanceMonitor for tracking render times

Expected Performance Improvements

Before Optimizations

  • Initial Load: ~3-5 seconds (large bundle)
  • Section Switching: ~2 seconds (full re-render)
  • Search: Immediate but blocks UI
  • Memory Usage: High (no cleanup)

After Optimizations

  • Initial Load: ~1-2 seconds (code splitting)
  • Section Switching: ~200-500ms (cached data + memoization)
  • Search: Debounced, non-blocking
  • Memory Usage: Optimized (caching with expiry)

Usage Instructions

  1. Development: Run npm run dev to start with optimizations
  2. Bundle Analysis: Run npm run analyze to see bundle composition
  3. Performance Monitoring: Press Ctrl+Shift+P in development mode
  4. Cache Management: Cache automatically expires after 5 minutes

Future Optimizations

  1. Virtual Scrolling: For very large datasets
  2. Service Worker: For offline caching
  3. Image Optimization: Lazy loading for provider logos
  4. Database Integration: Move from JSON to database for better performance
  5. CDN Integration: For static assets

Monitoring & Maintenance

  • Monitor bundle sizes with each deployment
  • Track Core Web Vitals in production
  • Regular cache cleanup and optimization
  • Performance regression testing