Performance Problems

Diagnosing and resolving performance issues, optimization strategies, and monitoring techniques.

Performance Problems

Guide to identifying, diagnosing, and resolving performance issues in your application.

Performance Monitoring

Core Web Vitals

Key Metrics to Monitor:

// Monitor Core Web Vitals
export function measureWebVitals() {
  // Largest Contentful Paint (LCP)
  new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries()
    const lastEntry = entries[entries.length - 1]
    console.log('LCP:', lastEntry.startTime)
  }).observe({ entryTypes: ['largest-contentful-paint'] })

  // First Input Delay (FID)
  new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries()
    entries.forEach((entry) => {
      console.log('FID:', entry.processingStart - entry.startTime)
    })
  }).observe({ entryTypes: ['first-input'] })

  // Cumulative Layout Shift (CLS)
  let clsValue = 0
  new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
      if (!entry.hadRecentInput) {
        clsValue += entry.value
      }
    }
    console.log('CLS:', clsValue)
  }).observe({ entryTypes: ['layout-shift'] })
}

Performance Monitoring Tools

// Next.js performance monitoring
export function reportWebVitals(metric) {
  console.log(metric)
  
  // Send to analytics
  if (metric.label === 'web-vital') {
    gtag('event', metric.name, {
      event_category: 'Web Vitals',
      value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
      event_label: metric.id,
      non_interaction: true,
    })
  }
}

// Add to pages/_app.tsx
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

MyApp.reportWebVitals = reportWebVitals

Bundle Size Optimization

Analyzing Bundle Size

# Analyze bundle size
npm run build
npx @next/bundle-analyzer

# Check specific dependencies
npx bundlephobia <package-name>

# Webpack bundle analyzer
npm install --save-dev webpack-bundle-analyzer

Code Splitting Strategies

// Dynamic imports for route-based splitting
const DynamicComponent = dynamic(
  () => import('../components/HeavyComponent'),
  {
    loading: () => <p>Loading...</p>,
    ssr: false, // Disable SSR for client-only components
  }
)

// Conditional loading
const AdminPanel = dynamic(
  () => import('../components/AdminPanel'),
  {
    loading: () => <div>Loading admin panel...</div>,
  }
)

export default function Dashboard({ user }) {
  return (
    <div>
      <h1>Dashboard</h1>
      {user.isAdmin && <AdminPanel />}
    </div>
  )
}

// Library code splitting
const Chart = dynamic(
  () => import('react-chartjs-2').then(mod => mod.Chart),
  { ssr: false }
)

// Multiple dynamic imports
const [ComponentA, ComponentB] = await Promise.all([
  import('./ComponentA'),
  import('./ComponentB'),
])

Tree Shaking Optimization

// next.config.js
const nextConfig = {
  webpack: (config, { isServer }) => {
    // Optimize imports
    config.resolve.alias = {
      ...config.resolve.alias,
      'lodash': 'lodash-es',
    }

    // Tree shaking for specific libraries
    config.module.rules.push({
      test: /\.js$/,
      include: /node_modules\/(some-library)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [['@babel/preset-env', { modules: false }]],
        },
      },
    })

    return config
  },
  
  // Reduce JavaScript bundle size
  experimental: {
    optimizePackageImports: ['lucide-react', 'date-fns'],
  },
}

Image Optimization

Next.js Image Component

import Image from 'next/image'

// Optimized image loading
export function ProductImage({ product }) {
  return (
    <Image
      src={product.imageUrl}
      alt={product.name}
      width={300}
      height={200}
      priority={product.featured} // Prioritize above-fold images
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,..." // Low-quality placeholder
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      quality={85} // Optimize quality vs size
    />
  )
}

// Responsive images
export function ResponsiveImage({ src, alt }) {
  return (
    <Image
      src={src}
      alt={alt}
      fill
      style={{ objectFit: 'cover' }}
      sizes="(max-width: 640px) 100vw, 
             (max-width: 1024px) 50vw, 
             25vw"
    />
  )
}

Image Format Optimization

// next.config.js
const nextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
    domains: ['example.com', 'cdn.example.com'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 31536000, // 1 year
  },
}

Database Performance

Query Optimization

// Efficient data fetching
export async function getProductsWithInventory() {
  // Single query with joins instead of multiple queries
  const { data } = await supabase
    .from('products')
    .select(`
      id,
      name,
      sku,
      price,
      inventory!inner(
        quantity_on_hand,
        warehouse:warehouses(name, location)
      )
    `)
    .eq('is_active', true)
    .gte('inventory.quantity_on_hand', 1)
    .order('name')
    .limit(50)

  return data
}

// Pagination for large datasets
export async function getPaginatedProducts(page = 1, limit = 20) {
  const offset = (page - 1) * limit
  
  const { data, count } = await supabase
    .from('products')
    .select('*', { count: 'exact' })
    .range(offset, offset + limit - 1)
    .order('created_at', { ascending: false })

  return {
    products: data,
    totalCount: count,
    currentPage: page,
    totalPages: Math.ceil(count / limit),
  }
}

Caching Strategies

// React Query for server state management
import { useQuery, useQueryClient } from '@tanstack/react-query'

export function useProducts() {
  return useQuery({
    queryKey: ['products'],
    queryFn: fetchProducts,
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  })
}

// SWR for data fetching
import useSWR from 'swr'

export function useProductData(productId) {
  const { data, error, isLoading } = useSWR(
    productId ? `products/${productId}` : null,
    fetchProduct,
    {
      refreshInterval: 30000, // Refresh every 30 seconds
      revalidateOnFocus: false,
      dedupingInterval: 2000,
    }
  )

  return { product: data, error, isLoading }
}

// Next.js caching
export async function getStaticProps() {
  const products = await fetchProducts()

  return {
    props: { products },
    revalidate: 60, // Regenerate page every 60 seconds
  }
}

Frontend Performance

Component Optimization

import { memo, useMemo, useCallback } from 'react'

// Memoize expensive calculations
export const ProductList = memo(function ProductList({ products, filters }) {
  const filteredProducts = useMemo(() => {
    return products.filter(product => {
      return filters.category === 'all' || product.category === filters.category
    })
  }, [products, filters.category])

  const handleProductClick = useCallback((productId) => {
    // Handle click
    console.log('Product clicked:', productId)
  }, [])

  return (
    <div>
      {filteredProducts.map(product => (
        <ProductCard
          key={product.id}
          product={product}
          onClick={handleProductClick}
        />
      ))}
    </div>
  )
})

// Optimize re-renders with React.memo
export const ProductCard = memo(function ProductCard({ product, onClick }) {
  return (
    <div onClick={() => onClick(product.id)}>
      <h3>{product.name}</h3>
      <p>{product.description}</p>
    </div>
  )
})

Virtual Scrolling for Large Lists

import { FixedSizeList as List } from 'react-window'

export function VirtualizedProductList({ products }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      <ProductCard product={products[index]} />
    </div>
  )

  return (
    <List
      height={600}
      itemCount={products.length}
      itemSize={120}
      width="100%"
    >
      {Row}
    </List>
  )
}

Memory Management

Memory Leak Prevention

// Cleanup subscriptions
export function useRealtimeUpdates(productId) {
  useEffect(() => {
    const subscription = supabase
      .channel(`product-${productId}`)
      .on('postgres_changes', 
        { event: '*', schema: 'public', table: 'products' },
        handleUpdate
      )
      .subscribe()

    // Cleanup on unmount
    return () => {
      subscription.unsubscribe()
    }
  }, [productId])
}

// Cleanup timers and intervals
export function usePolling(callback, interval) {
  useEffect(() => {
    const id = setInterval(callback, interval)
    return () => clearInterval(id)
  }, [callback, interval])
}

// Abort API requests on component unmount
export function useApiCall(url) {
  const [data, setData] = useState(null)

  useEffect(() => {
    const controller = new AbortController()

    fetch(url, { signal: controller.signal })
      .then(response => response.json())
      .then(setData)
      .catch(error => {
        if (error.name !== 'AbortError') {
          console.error('Fetch error:', error)
        }
      })

    return () => controller.abort()
  }, [url])

  return data
}

Performance Testing

Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
  lhci:
    name: Lighthouse
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run build
      - run: npm install -g @lhci/cli@0.8.x
      - run: lhci autorun
// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000'],
      startServerCommand: 'npm start',
    },
    assert: {
      assertions: {
        'categories:performance': ['warn', { minScore: 0.9 }],
        'categories:accessibility': ['error', { minScore: 0.9 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
}

Load Testing

// Load testing with Artillery
// artillery.yml
config:
  target: 'http://localhost:3000'
  phases:
    - duration: 60
      arrivalRate: 10
scenarios:
  - name: "Product browsing"
    flow:
      - get:
          url: "/"
      - get:
          url: "/products"
      - get:
          url: "/products/{{ $randomString() }}"

// Performance monitoring
export function trackPerformance() {
  // Track page load time
  window.addEventListener('load', () => {
    const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart
    console.log('Page load time:', loadTime)
  })

  // Track API response times
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.initiatorType === 'fetch') {
        console.log('API call duration:', entry.duration)
      }
    }
  })
  observer.observe({ entryTypes: ['resource'] })
}

Common Performance Issues

Hydration Mismatches

  • Symptom: Slow initial page load, flickering content
  • Solution: Ensure server and client render the same content

Large Bundle Sizes

  • Symptom: Slow JavaScript download and parsing
  • Solution: Code splitting, tree shaking, lazy loading

Unnecessary Re-renders

  • Symptom: Sluggish UI interactions
  • Solution: Use React.memo, useMemo, useCallback

Memory Leaks

  • Symptom: Increasing memory usage over time
  • Solution: Cleanup subscriptions, timers, and event listeners

Database N+1 Queries

  • Symptom: Multiple database queries for related data
  • Solution: Use joins, batch queries, or data loaders

For performance monitoring tools and advanced optimization techniques, refer to the Next.js Performance Documentation.