Performance Optimizations - DrunkOnJava/MaterialMDashboard GitHub Wiki

Performance Optimizations

This guide covers various performance optimization techniques implemented in Material Dashboard.

Build Optimizations

Code Splitting

The application uses automatic code splitting through dynamic imports:

// Route-based code splitting
const Dashboard = lazy(() => import('./screens/Dashboard'));
const Charts = lazy(() => import('./screens/Charts'));
const Forms = lazy(() => import('./screens/Forms'));

// Component-based code splitting
const HeavyComponent = lazy(() => import('./components/HeavyComponent'));

Bundle Optimization

Vite configuration for optimized bundles:

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Vendor chunk for core dependencies
          vendor: ['react', 'react-dom', 'react-router-dom'],
          // UI components chunk
          ui: [
            '@radix-ui/react-dialog',
            '@radix-ui/react-dropdown-menu',
            '@radix-ui/react-tooltip',
          ],
          // Charts chunk
          charts: ['chart.js', 'react-chartjs-2'],
        },
      },
    },
    // Minification
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
  },
});

Tree Shaking

Ensure proper tree shaking:

// ✅ Good - Named imports allow tree shaking
import { Button, Card } from '@/components/ui';

// ❌ Bad - Imports entire module
import * as UI from '@/components/ui';

Rendering Optimizations

React Suspense

Implement loading states with Suspense:

import { Suspense, lazy } from 'react';
import { DashboardSkeleton } from './components/DashboardSkeleton';

const Dashboard = lazy(() => import('./screens/Dashboard'));

function App() {
  return (
    <Suspense fallback={<DashboardSkeleton />}>
      <Dashboard />
    </Suspense>
  );
}

Memoization

Use React.memo for expensive components:

// Memoize components that receive stable props
export const ExpensiveChart = React.memo(({ data, options }) => {
  return <Line data={data} options={options} />;
}, (prevProps, nextProps) => {
  // Custom comparison function
  return prevProps.data === nextProps.data && 
         prevProps.options === nextProps.options;
});

// useMemo for expensive calculations
const processedData = useMemo(() => {
  return heavyDataProcessing(rawData);
}, [rawData]);

// useCallback for stable function references
const handleClick = useCallback((id: string) => {
  setSelectedId(id);
}, []);

Virtual Scrolling

Implement virtual scrolling for large lists:

import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

Asset Optimization

Image Optimization

Lazy Loading

Implemented lazy loading with IntersectionObserver:

// LazyImage component
import { useState, useEffect, useRef } from 'react';

export const LazyImage = ({ src, alt, placeholder, ...props }) => {
  const [imageSrc, setImageSrc] = useState(placeholder);
  const imgRef = useRef<HTMLImageElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setImageSrc(src);
            observer.unobserve(entry.target);
          }
        });
      },
      { threshold: 0.1, rootMargin: '200px' }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, [src]);

  return <img ref={imgRef} src={imageSrc} alt={alt} {...props} />;
};

Image Formats

Use modern image formats:

<picture>
  <source srcSet="/image.webp" type="image/webp" />
  <source srcSet="/image.jpg" type="image/jpeg" />
  <img src="/image.jpg" alt="Description" loading="lazy" />
</picture>

Font Optimization

Optimize font loading:

/* Preload critical fonts */
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>

/* Font display swap */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-display: swap;
}

State Management Optimization

Context Optimization

Split contexts to prevent unnecessary re-renders:

// ❌ Bad - Single large context
const AppContext = createContext({
  user: null,
  theme: 'light',
  settings: {},
  // ... many more values
});

// ✅ Good - Separate contexts
const UserContext = createContext({ user: null });
const ThemeContext = createContext({ theme: 'light' });
const SettingsContext = createContext({ settings: {} });

State Colocation

Keep state close to where it's used:

// ✅ Good - Local state for local UI
function SearchBar() {
  const [query, setQuery] = useState('');
  
  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  );
}

// ❌ Bad - Global state for local UI
// Don't put query in global state if only SearchBar uses it

Network Optimization

API Caching

Implement caching strategies:

// Simple cache implementation
const cache = new Map();

async function fetchWithCache(url: string, options = {}) {
  const cacheKey = `${url}-${JSON.stringify(options)}`;
  
  if (cache.has(cacheKey)) {
    return cache.get(cacheKey);
  }
  
  const response = await fetch(url, options);
  const data = await response.json();
  
  cache.set(cacheKey, data);
  return data;
}

// With expiration
class CacheWithExpiry {
  private cache = new Map();
  
  set(key: string, value: any, ttl: number) {
    const expires = Date.now() + ttl;
    this.cache.set(key, { value, expires });
  }
  
  get(key: string) {
    const item = this.cache.get(key);
    if (!item) return null;
    
    if (Date.now() > item.expires) {
      this.cache.delete(key);
      return null;
    }
    
    return item.value;
  }
}

Request Debouncing

Debounce API requests:

import { debounce } from 'lodash-es';

function SearchComponent() {
  const [results, setResults] = useState([]);
  
  const debouncedSearch = useMemo(
    () => debounce(async (query: string) => {
      const data = await searchAPI(query);
      setResults(data);
    }, 300),
    []
  );
  
  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    debouncedSearch(e.target.value);
  };
  
  return <input onChange={handleSearch} />;
}

CSS Optimization

Critical CSS

Extract and inline critical CSS:

<!-- Inline critical CSS -->
<style>
  /* Critical above-the-fold styles */
  .hero { ... }
  .nav { ... }
</style>

<!-- Load non-critical CSS asynchronously -->
<link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

CSS-in-JS Optimization

Use CSS modules or Tailwind for better performance:

// ✅ Good - Tailwind utilities (compiled at build time)
<div className="flex items-center justify-center p-4">

// ❌ Avoid - Runtime CSS-in-JS for static styles
const StyledDiv = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
`;

Purge Unused CSS

Tailwind configuration for CSS purging:

// tailwind.config.js
module.exports = {
  content: [
    './index.html',
    './src/**/*.{js,ts,jsx,tsx}',
  ],
  // Tailwind automatically purges unused styles in production
}

Runtime Performance

Event Handler Optimization

Optimize event handlers:

// ✅ Good - Event delegation
function List({ items }) {
  const handleClick = (e: React.MouseEvent) => {
    const id = e.currentTarget.getAttribute('data-id');
    if (id) {
      handleItemClick(id);
    }
  };
  
  return (
    <ul onClick={handleClick}>
      {items.map(item => (
        <li key={item.id} data-id={item.id}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

// ❌ Bad - Handler for each item
function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => handleItemClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

Animation Performance

Use CSS transforms for animations:

/* ✅ Good - GPU accelerated */
.slide-in {
  transform: translateX(100%);
  transition: transform 0.3s ease;
}

.slide-in.active {
  transform: translateX(0);
}

/* ❌ Bad - Triggers layout */
.slide-in {
  left: 100%;
  transition: left 0.3s ease;
}

Monitoring and Profiling

Performance Metrics

Monitor key metrics:

// Web Vitals monitoring
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  // Send to your analytics service
  console.log(metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

React DevTools Profiler

Use React DevTools Profiler to identify performance bottlenecks:

  1. Open React DevTools
  2. Navigate to Profiler tab
  3. Start recording
  4. Interact with your app
  5. Stop recording and analyze

Chrome DevTools

Performance profiling:

  1. Open Chrome DevTools
  2. Go to Performance tab
  3. Start recording
  4. Interact with your app
  5. Stop recording
  6. Analyze flame chart

PWA Optimizations

Service Worker

Implement caching with service worker:

// sw.js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/',
        '/styles/main.css',
        '/scripts/main.js',
        '/offline.html',
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

App Shell

Implement app shell architecture:

// Cache app shell
const APP_SHELL_CACHE = 'app-shell-v1';
const urlsToCache = [
  '/',
  '/manifest.json',
  '/static/css/main.css',
  '/static/js/main.js',
];

Best Practices Checklist

Before Deployment

  • Run production build and check bundle sizes
  • Test with slow network throttling
  • Verify lazy loading is working
  • Check for memory leaks
  • Audit with Lighthouse
  • Test on real devices
  • Monitor Web Vitals

Performance Budget

Set performance budgets:

// vite.config.ts
export default {
  build: {
    rollupOptions: {
      output: {
        // Warn if chunks exceed size
        maxParallelFileOps: 2,
        entryFileNames: '[name].[hash].js',
        chunkFileNames: '[name].[hash].js',
      },
    },
    // Fail build if bundle exceeds limit
    chunkSizeWarningLimit: 500, // 500kb
  },
};

Conclusion

Performance optimization is an ongoing process. Regular monitoring and profiling help identify bottlenecks and opportunities for improvement. Focus on:

  1. Initial load performance
  2. Runtime performance
  3. Network optimization
  4. Asset optimization
  5. User experience metrics

Remember to measure before and after optimizations to ensure improvements are effective.

Resources

Next Steps

⚠️ **GitHub.com Fallback** ⚠️