Performance Optimization

Optimize application performance for production deployment with caching, bundling, and monitoring strategies.

Performance Optimization

Comprehensive guide for optimizing Smart Shelf application performance in production environments.

Performance Metrics

Core Web Vitals

Core Web Vitals are essential metrics for measuring user experience:

  • Largest Contentful Paint (LCP): ≤ 2.5 seconds
  • First Input Delay (FID): ≤ 100 milliseconds
  • Cumulative Layout Shift (CLS): ≤ 0.1
  • First Contentful Paint (FCP): ≤ 1.8 seconds
  • Time to First Byte (TTFB): ≤ 600 milliseconds

Performance Monitoring

Web Vitals Implementation

// lib/web-vitals.ts
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'

interface WebVitalMetric {
  id: string
  name: string
  value: number
  delta: number
  rating: 'good' | 'needs-improvement' | 'poor'
}

export function reportWebVitals() {
  getCLS(sendToAnalytics)
  getFID(sendToAnalytics)
  getFCP(sendToAnalytics)
  getLCP(sendToAnalytics)
  getTTFB(sendToAnalytics)
}

function sendToAnalytics(metric: WebVitalMetric) {
  console.log('Web Vital:', metric)
  
  // Send to analytics service
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', metric.name, {
      event_category: 'Web Vitals',
      event_label: metric.id,
      value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
      non_interaction: true,
    })
  }
}

Performance Observer

// lib/performance-observer.ts
export function initPerformanceObserver() {
  if (typeof window === 'undefined') return

  // Monitor navigation timing
  const navigationObserver = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.entryType === 'navigation') {
        const navEntry = entry as PerformanceNavigationTiming
        console.log('Navigation Performance:', {
          domContentLoaded: navEntry.domContentLoadedEventEnd - navEntry.domContentLoadedEventStart,
          loadComplete: navEntry.loadEventEnd - navEntry.loadEventStart,
          totalTime: navEntry.loadEventEnd - navEntry.fetchStart,
        })
      }
    }
  })

  navigationObserver.observe({ entryTypes: ['navigation'] })

  // Monitor resource timing
  const resourceObserver = new PerformanceObserver((list) => {
    const slowResources = list.getEntries().filter(entry => entry.duration > 1000)
    if (slowResources.length > 0) {
      console.warn('Slow resources detected:', slowResources)
    }
  })

  resourceObserver.observe({ entryTypes: ['resource'] })
}

Bundle Optimization

Code Splitting

Dynamic Imports

// Dynamic imports for large components
const AnalyticsPage = dynamic(() => import('@/components/analytics/analytics-page'), {
  loading: () => <div className="flex items-center justify-center p-8">
    <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
  </div>,
  ssr: false // Disable SSR for client-heavy components
})

// Conditional loading
const AdminPanel = dynamic(() => import('@/components/admin/admin-panel'), {
  loading: () => <AdminPanelSkeleton />,
  ssr: false
})

// Component with error boundary
const BarcodeScanner = dynamic(
  () => import('@/components/barcode/scanner').catch(() => ({
    default: () => <div>Barcode scanner unavailable</div>
  })),
  { ssr: false }
)

Route-Based Splitting

// app/dashboard/page.tsx
import dynamic from 'next/dynamic'

// Split dashboard sections
const InventorySection = dynamic(() => import('@/components/dashboard/inventory-section'))
const OrdersSection = dynamic(() => import('@/components/dashboard/orders-section'))
const AnalyticsSection = dynamic(() => import('@/components/dashboard/analytics-section'))

export default function DashboardPage() {
  return (
    <div className="space-y-6">
      <InventorySection />
      <OrdersSection />
      <AnalyticsSection />
    </div>
  )
}

Tree Shaking

Optimal Import Patterns

// ✅ Named imports for tree shaking
import { Button, Card, Input } from '@/components/ui'
import { formatDate, calculateTotal } from '@/lib/utils'
import { toast } from 'sonner'

// ❌ Avoid default imports of large libraries
import * as _ from 'lodash' // Imports entire library
import { debounce } from 'lodash' // ✅ Better

// ✅ Use specific imports
import debounce from 'lodash/debounce' // ✅ Best

Bundle Analysis

# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer

# Configure in next.config.mjs
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer({
  // Your Next.js config
})

# Run analysis
ANALYZE=true npm run build

Third-Party Libraries

Selective Loading

// Load only required chart types
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  BarElement,
  LineElement,
  PointElement,
  Title,
  Tooltip,
  Legend,
} from 'chart.js'

ChartJS.register(
  CategoryScale,
  LinearScale,
  BarElement,
  LineElement,
  PointElement,
  Title,
  Tooltip,
  Legend
)

External Script Optimization

// app/layout.tsx
import Script from 'next/script'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        
        {/* Load analytics after page is interactive */}
        <Script
          src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
          strategy="afterInteractive"
        />
        
        {/* Load non-critical scripts after page load */}
        <Script
          src="https://widget.intercom.io/widget/xyz123"
          strategy="lazyOnload"
        />
      </body>
    </html>
  )
}

Image Optimization

Next.js Image Component

import Image from 'next/image'

// Optimized product image
export function ProductImage({ src, alt, size = 200 }: ProductImageProps) {
  return (
    <Image
      src={src}
      alt={alt}
      width={size}
      height={size}
      className="rounded-lg object-cover"
      priority={false} // Set to true for above-the-fold images
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..." // Low-quality placeholder
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
    />
  )
}

// Hero image with priority loading
export function HeroImage() {
  return (
    <Image
      src="/hero-image.jpg"
      alt="Smart Shelf Dashboard"
      width={1920}
      height={1080}
      priority={true} // Load immediately
      className="w-full h-auto"
    />
  )
}

Image Configuration

// next.config.mjs
const nextConfig = {
  images: {
    domains: ['your-domain.com', 'supabase.co', 'amazonaws.com'],
    formats: ['image/webp', 'image/avif'],
    minimumCacheTTL: 31536000, // 1 year
    dangerouslyAllowSVG: true,
    contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}

Responsive Images

// components/responsive-image.tsx
interface ResponsiveImageProps {
  src: string
  alt: string
  className?: string
}

export function ResponsiveImage({ src, alt, className }: ResponsiveImageProps) {
  return (
    <Image
      src={src}
      alt={alt}
      width={0}
      height={0}
      sizes="100vw"
      className={`w-full h-auto ${className}`}
    />
  )
}

Caching Strategies

HTTP Caching

Static Assets

// next.config.mjs
const nextConfig = {
  async headers() {
    return [
      {
        source: '/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        source: '/_next/image',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
    ]
  },
}

API Response Caching

// app/api/products/route.ts
export async function GET() {
  const products = await getProducts()
  
  return Response.json(products, {
    headers: {
      'Cache-Control': 's-maxage=300, stale-while-revalidate=600',
      'CDN-Cache-Control': 'max-age=3600',
      'Vercel-CDN-Cache-Control': 'max-age=86400',
    }
  })
}

Data Caching

React Query Implementation

// lib/react-query.ts
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      cacheTime: 10 * 60 * 1000, // 10 minutes
      retry: 3,
      retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
    },
  },
})

// hooks/use-products.ts
export function useProducts(params?: ProductParams) {
  return useQuery({
    queryKey: ['products', params],
    queryFn: () => fetchProducts(params),
    staleTime: 5 * 60 * 1000,
    cacheTime: 30 * 60 * 1000,
  })
}

Memory Caching

// lib/cache.ts
class MemoryCache {
  private cache = new Map<string, { value: any; expires: number }>()

  set(key: string, value: any, ttl: number = 300000) { // 5 minutes default
    this.cache.set(key, {
      value,
      expires: Date.now() + ttl,
    })
  }

  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
  }

  clear() {
    this.cache.clear()
  }
}

export const memoryCache = new MemoryCache()

Service Worker Caching

// public/sw.js
const CACHE_NAME = 'smart-shelf-v1'
const urlsToCache = [
  '/',
  '/dashboard',
  '/products',
  '/static/css/main.css',
  '/static/js/main.js',
]

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => cache.addAll(urlsToCache))
  )
})

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => {
        // Return cached version or fetch from network
        return response || fetch(event.request)
      })
  )
})

Database Performance

Query Optimization

Efficient Queries

// ❌ N+1 Query Problem
async function getProductsWithCategories() {
  const products = await supabase.from('products').select('*')
  
  for (const product of products.data || []) {
    const category = await supabase
      .from('categories')
      .select('*')
      .eq('id', product.category_id)
      .single()
    product.category = category.data
  }
  
  return products
}

// ✅ Optimized with joins
async function getProductsWithCategories() {
  return supabase
    .from('products')
    .select(`
      *,
      categories (
        id,
        name,
        description
      )
    `)
}

Pagination and Limiting

// Efficient pagination
export async function getProducts(page: number = 1, limit: number = 20) {
  const from = (page - 1) * limit
  const to = from + limit - 1
  
  return supabase
    .from('products')
    .select('*', { count: 'exact' })
    .range(from, to)
    .order('created_at', { ascending: false })
}

// Search with debouncing
export function useProductSearch(query: string) {
  const debouncedQuery = useDebounce(query, 300)
  
  return useQuery({
    queryKey: ['products', 'search', debouncedQuery],
    queryFn: () => searchProducts(debouncedQuery),
    enabled: debouncedQuery.length >= 3,
  })
}

Connection Pooling

// lib/supabase/server.ts
import { createClient } from '@supabase/supabase-js'

// Optimized server-side client
export const supabaseServer = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
  {
    db: {
      schema: 'public',
    },
    auth: {
      autoRefreshToken: false,
      persistSession: false,
    },
    global: {
      headers: {
        'X-Client-Info': 'smart-shelf-server@1.0.0',
      },
    },
  }
)

Runtime Performance

React Optimization

Memoization

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

// Memoized component
export const ProductCard = memo(function ProductCard({ product }: ProductCardProps) {
  const formattedPrice = useMemo(() => {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
    }).format(product.price)
  }, [product.price])

  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>{formattedPrice}</p>
    </div>
  )
})

// Memoized callback
export function ProductList({ products, onProductClick }: ProductListProps) {
  const handleClick = useCallback((productId: string) => {
    onProductClick(productId)
  }, [onProductClick])

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

Virtual Scrolling

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

interface VirtualizedListProps {
  items: Product[]
  height: number
  itemHeight: number
}

export function VirtualizedProductList({ items, height, itemHeight }: VirtualizedListProps) {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <ProductCard product={items[index]} />
    </div>
  )

  return (
    <List
      height={height}
      itemCount={items.length}
      itemSize={itemHeight}
      overscanCount={5}
    >
      {Row}
    </List>
  )
}

State Management Optimization

// Optimized context provider
export const ProductsProvider = memo(function ProductsProvider({ children }: PropsWithChildren) {
  const [products, setProducts] = useState<Product[]>([])
  const [loading, setLoading] = useState(false)

  const contextValue = useMemo(() => ({
    products,
    loading,
    setProducts,
    setLoading,
  }), [products, loading])

  return (
    <ProductsContext.Provider value={contextValue}>
      {children}
    </ProductsContext.Provider>
  )
})

Monitoring and Analytics

Performance Monitoring

// lib/performance-monitor.ts
export class PerformanceMonitor {
  private metrics: Map<string, number> = new Map()

  startTimer(name: string) {
    this.metrics.set(name, performance.now())
  }

  endTimer(name: string) {
    const startTime = this.metrics.get(name)
    if (startTime) {
      const duration = performance.now() - startTime
      console.log(`${name}: ${duration.toFixed(2)}ms`)
      this.metrics.delete(name)
      return duration
    }
    return 0
  }

  measureFunction<T>(name: string, fn: () => T): T {
    this.startTimer(name)
    const result = fn()
    this.endTimer(name)
    return result
  }

  async measureAsyncFunction<T>(name: string, fn: () => Promise<T>): Promise<T> {
    this.startTimer(name)
    const result = await fn()
    this.endTimer(name)
    return result
  }
}

export const performanceMonitor = new PerformanceMonitor()

Real User Monitoring

// lib/rum.ts
export function initRUM() {
  if (typeof window === 'undefined') return

  // Monitor page load performance
  window.addEventListener('load', () => {
    const navTiming = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
    
    const metrics = {
      domContentLoaded: navTiming.domContentLoadedEventEnd - navTiming.domContentLoadedEventStart,
      loadComplete: navTiming.loadEventEnd - navTiming.loadEventStart,
      totalTime: navTiming.loadEventEnd - navTiming.fetchStart,
    }

    // Send metrics to analytics
    console.log('Page Load Metrics:', metrics)
  })

  // Monitor user interactions
  ['click', 'keydown'].forEach(eventType => {
    document.addEventListener(eventType, (event) => {
      const target = event.target as HTMLElement
      console.log(`User interaction: ${eventType} on ${target.tagName}`)
    })
  })
}

Performance Best Practices

1. Optimize Critical Rendering Path

  • Minimize render-blocking resources
  • Inline critical CSS
  • Defer non-critical JavaScript
  • Optimize web fonts loading

2. Implement Efficient Caching

  • Use appropriate cache headers
  • Implement service worker caching
  • Cache API responses strategically
  • Use CDN for static assets

3. Optimize Images and Media

  • Use Next.js Image component
  • Implement responsive images
  • Use modern image formats (WebP, AVIF)
  • Lazy load non-critical images

4. Database Performance

  • Use appropriate indexes
  • Optimize queries with joins
  • Implement connection pooling
  • Use read replicas for scaling

5. Monitor and Measure

  • Implement Core Web Vitals tracking
  • Use Real User Monitoring (RUM)
  • Set up performance budgets
  • Regular performance audits