Frontend Performance Optimization

Frontend performance optimization techniques including bundle optimization, asset management, and runtime performance.

Frontend Performance Optimization

Frontend performance is critical for user experience in Smart Shelf. This section covers comprehensive strategies for optimizing client-side performance, from initial bundle loading to runtime efficiency.

Bundle Optimization

Code Splitting Strategy

Code splitting reduces initial bundle size by loading code on demand, improving initial page load times.

Route-Based Code Splitting

// Dynamic imports for route-based splitting
const Dashboard = dynamic(() => import('./dashboard'), {
  loading: () => <PageSkeleton />,
  ssr: false
})

const InventoryPage = dynamic(() => import('./inventory'), {
  loading: () => <InventoryPageSkeleton />,
  ssr: true // Enable SSR for critical pages
})

const ReportsPage = dynamic(() => import('./reports'), {
  loading: () => <ReportsSkeleton />,
  ssr: false // Disable SSR for heavy analytical pages
})

Component-Based Code Splitting

// Heavy components loaded on demand
const BarcodeScannerModal = dynamic(
  () => import('@/components/barcode/scanner-modal'),
  {
    loading: () => <div className="animate-pulse">Loading scanner...</div>,
    ssr: false // Camera features require client-side rendering
  }
)

const AdvancedReportBuilder = dynamic(
  () => import('@/components/reports/advanced-builder'),
  {
    loading: () => <ReportBuilderSkeleton />,
    ssr: false
  }
)

// Chart library splitting
const Chart = dynamic(() => import('recharts').then(mod => mod.Chart), {
  loading: () => <ChartSkeleton />
})

Library-Level Code Splitting

// Split large libraries
const ExcelExport = dynamic(
  () => import('@/lib/excel-export').then(mod => mod.ExcelExport),
  { ssr: false }
)

// Conditional library loading
const PDFGenerator = dynamic(
  () => import('@/lib/pdf-generator'),
  {
    loading: () => <div>Preparing PDF...</div>,
    ssr: false
  }
)

Tree Shaking Configuration

Eliminate unused code from the final bundle through proper tree shaking configuration.

Next.js Configuration

// next.config.mjs
const nextConfig = {
  experimental: {
    optimizePackageImports: [
      '@radix-ui/react-icons',
      'lucide-react',
      'recharts',
      'date-fns',
      'lodash-es'
    ]
  },
  webpack: (config, { dev, isServer }) => {
    // Enable tree shaking
    config.optimization.sideEffects = false
    
    // Optimize production builds
    if (!dev && !isServer) {
      config.optimization.splitChunks = {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          },
          common: {
            name: 'common',
            minChunks: 2,
            chunks: 'all',
          },
        },
      }
    }
    
    return config
  }
}

export default nextConfig

Package.json Optimizations

{
  "sideEffects": false,
  "dependencies": {
    "lodash-es": "^4.17.21",
    "date-fns": "^2.29.3"
  },
  "scripts": {
    "build:analyze": "ANALYZE=true next build",
    "bundle:analyze": "npx webpack-bundle-analyzer .next/static/chunks/*.js"
  }
}

Bundle Analysis and Monitoring

Regular bundle analysis helps identify optimization opportunities.

Bundle Analysis Script

#!/bin/bash
# scripts/analyze-bundle.sh

echo "Building production bundle..."
npm run build

echo "Analyzing bundle size..."
npx webpack-bundle-analyzer .next/static/chunks/*.js --mode static --report bundle-report.html

echo "Checking for duplicate dependencies..."
npx depcheck

echo "Bundle size summary:"
du -sh .next/static/chunks/

Bundle Size Monitoring

// lib/monitoring/bundle-monitoring.ts
export function trackBundleMetrics() {
  if (typeof window !== 'undefined' && 'performance' in window) {
    window.addEventListener('load', () => {
      const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
      
      const metrics = {
        domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
        loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
        resourceCount: performance.getEntriesByType('resource').length,
        transferSize: performance.getEntriesByType('resource').reduce(
          (total, resource: any) => total + (resource.transferSize || 0), 0
        )
      }
      
      console.log('Bundle Performance Metrics:', metrics)
      // Send to analytics service
    })
  }
}

Asset Optimization

Image Optimization

Smart Shelf implements comprehensive image optimization for better performance.

Optimized Image Component

// components/ui/optimized-image.tsx
import Image from 'next/image'
import { useState } from 'react'

interface OptimizedImageProps {
  src: string
  alt: string
  width: number
  height: number
  priority?: boolean
  className?: string
  onLoad?: () => void
}

export function OptimizedImage({ 
  src, 
  alt, 
  width, 
  height, 
  priority = false,
  className,
  onLoad
}: OptimizedImageProps) {
  const [isLoading, setIsLoading] = useState(true)

  return (
    <div className={`relative ${className}`}>
      <Image
        src={src}
        alt={alt}
        width={width}
        height={height}
        priority={priority}
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        style={{
          width: '100%',
          height: 'auto',
        }}
        placeholder="blur"
        blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
        onLoad={() => {
          setIsLoading(false)
          onLoad?.()
        }}
        className={`
          transition-opacity duration-300
          ${isLoading ? 'opacity-0' : 'opacity-100'}
        `}
      />
      {isLoading && (
        <div className="absolute inset-0 bg-gray-200 animate-pulse rounded" />
      )}
    </div>
  )
}

Image Optimization Configuration

// next.config.mjs
const nextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
    domains: ['your-cdn-domain.com'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
  }
}

Font Optimization

Optimize font loading for better performance and user experience.

Font Loading Strategy

// app/layout.tsx
import { Inter, JetBrains_Mono } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  preload: true,
  variable: '--font-inter',
  fallback: ['system-ui', 'arial']
})

const jetbrainsMono = JetBrains_Mono({
  subsets: ['latin'],
  display: 'swap',
  preload: false, // Only load when needed
  variable: '--font-mono',
  fallback: ['Courier New', 'monospace']
})

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={`${inter.variable} ${jetbrainsMono.variable}`}>
      <head>
        {/* Preload critical fonts */}
        <link
          rel="preload"
          href="/fonts/inter-var.woff2"
          as="font"
          type="font/woff2"
          crossOrigin="anonymous"
        />
      </head>
      <body className="font-sans">{children}</body>
    </html>
  )
}

Custom Font Loading Hook

// hooks/use-font-loading.ts
import { useState, useEffect } from 'react'

export function useFontLoading() {
  const [fontsLoaded, setFontsLoaded] = useState(false)

  useEffect(() => {
    if ('fonts' in document) {
      Promise.all([
        document.fonts.load('400 1em Inter'),
        document.fonts.load('500 1em Inter'),
        document.fonts.load('600 1em Inter'),
      ]).then(() => {
        setFontsLoaded(true)
      })
    } else {
      // Fallback for browsers without FontFace API
      setTimeout(() => setFontsLoaded(true), 1000)
    }
  }, [])

  return fontsLoaded
}

Runtime Performance

React Performance Optimization

Optimize React component rendering and updates for better runtime performance.

Memoization Strategies

// components/inventory/product-table.tsx
import React, { memo, useMemo, useCallback } from 'react'

interface Product {
  id: string
  name: string
  sku: string
  price: number
  quantity: number
}

interface ProductTableProps {
  products: Product[]
  onProductClick: (id: string) => void
  sortField: string
  sortDirection: 'asc' | 'desc'
}

// Memoize expensive components
const ProductRow = memo(({ 
  product, 
  onClick 
}: { 
  product: Product
  onClick: (id: string) => void 
}) => {
  const handleClick = useCallback(() => {
    onClick(product.id)
  }, [product.id, onClick])

  return (
    <tr onClick={handleClick} className="hover:bg-gray-50 cursor-pointer">
      <td>{product.name}</td>
      <td>{product.sku}</td>
      <td>${product.price.toFixed(2)}</td>
      <td>{product.quantity}</td>
    </tr>
  )
})

export const ProductTable = memo(({ 
  products, 
  onProductClick, 
  sortField, 
  sortDirection 
}: ProductTableProps) => {
  // Memoize expensive calculations
  const sortedProducts = useMemo(() => {
    return [...products].sort((a, b) => {
      const aValue = a[sortField as keyof Product]
      const bValue = b[sortField as keyof Product]
      
      if (sortDirection === 'asc') {
        return aValue > bValue ? 1 : -1
      }
      return aValue < bValue ? 1 : -1
    })
  }, [products, sortField, sortDirection])

  // Stable callback references
  const handleProductClick = useCallback((id: string) => {
    onProductClick(id)
  }, [onProductClick])

  return (
    <table className="w-full">
      <thead>
        <tr>
          <th>Name</th>
          <th>SKU</th>
          <th>Price</th>
          <th>Quantity</th>
        </tr>
      </thead>
      <tbody>
        {sortedProducts.map(product => (
          <ProductRow 
            key={product.id} 
            product={product}
            onClick={handleProductClick}
          />
        ))}
      </tbody>
    </table>
  )
})

Virtual Scrolling for Large Lists

Handle large datasets efficiently with virtual scrolling.

Virtual Scrolling Implementation

// components/ui/virtual-table.tsx
import { FixedSizeList as List } from 'react-window'
import { memo } from 'react'

interface VirtualTableProps<T> {
  items: T[]
  height: number
  itemHeight: number
  renderItem: (item: T, index: number) => React.ReactNode
  className?: string
}

export function VirtualTable<T>({ 
  items, 
  height, 
  itemHeight, 
  renderItem,
  className 
}: VirtualTableProps<T>) {
  const Row = memo(({ index, style }: { index: number; style: any }) => (
    <div style={style}>
      {renderItem(items[index], index)}
    </div>
  ))

  return (
    <div className={className}>
      <List
        height={height}
        itemCount={items.length}
        itemSize={itemHeight}
        width="100%"
        overscanCount={5} // Render 5 extra items for smooth scrolling
      >
        {Row}
      </List>
    </div>
  )
}

// Usage example
export function InventoryList({ inventory }: { inventory: InventoryItem[] }) {
  const renderInventoryItem = useCallback((item: InventoryItem, index: number) => (
    <div className="flex items-center p-4 border-b">
      <div className="flex-1">
        <h4 className="font-medium">{item.product.name}</h4>
        <p className="text-sm text-gray-500">{item.product.sku}</p>
      </div>
      <div className="text-right">
        <span className="font-semibold">{item.quantity}</span>
        <p className="text-sm text-gray-500">{item.warehouse.name}</p>
      </div>
    </div>
  ), [])

  return (
    <VirtualTable
      items={inventory}
      height={600}
      itemHeight={80}
      renderItem={renderInventoryItem}
      className="border rounded-lg"
    />
  )
}

Progressive Loading and Infinite Scroll

Implement progressive data loading for better perceived performance.

Progressive Loading Hook

// hooks/use-progressive-loading.ts
import { useState, useCallback, useRef, useEffect } from 'react'

interface UseProgressiveLoadingOptions<T> {
  fetchFunction: (page: number, pageSize: number) => Promise<T[]>
  pageSize?: number
  initialPage?: number
}

export function useProgressiveLoading<T>({
  fetchFunction,
  pageSize = 20,
  initialPage = 0
}: UseProgressiveLoadingOptions<T>) {
  const [items, setItems] = useState<T[]>([])
  const [loading, setLoading] = useState(false)
  const [hasMore, setHasMore] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const currentPage = useRef(initialPage)

  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return

    setLoading(true)
    setError(null)

    try {
      const newItems = await fetchFunction(currentPage.current, pageSize)
      
      setItems(prev => [...prev, ...newItems])
      setHasMore(newItems.length === pageSize)
      currentPage.current += 1
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to load data')
    } finally {
      setLoading(false)
    }
  }, [fetchFunction, loading, hasMore, pageSize])

  const reset = useCallback(() => {
    setItems([])
    setHasMore(true)
    setError(null)
    currentPage.current = initialPage
  }, [initialPage])

  // Load initial data
  useEffect(() => {
    loadMore()
  }, []) // Only run once

  return {
    items,
    loading,
    hasMore,
    error,
    loadMore,
    reset
  }
}

Infinite Scroll Component

// components/ui/infinite-scroll.tsx
import { useEffect, useRef } from 'react'

interface InfiniteScrollProps {
  children: React.ReactNode
  hasMore: boolean
  loading: boolean
  onLoadMore: () => void
  threshold?: number
  className?: string
}

export function InfiniteScroll({
  children,
  hasMore,
  loading,
  onLoadMore,
  threshold = 200,
  className
}: InfiniteScrollProps) {
  const sentinelRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const sentinel = sentinelRef.current
    if (!sentinel) return

    const observer = new IntersectionObserver(
      (entries) => {
        const entry = entries[0]
        if (entry.isIntersecting && hasMore && !loading) {
          onLoadMore()
        }
      },
      {
        rootMargin: `${threshold}px`,
      }
    )

    observer.observe(sentinel)

    return () => {
      observer.unobserve(sentinel)
    }
  }, [hasMore, loading, onLoadMore, threshold])

  return (
    <div className={className}>
      {children}
      {hasMore && (
        <div ref={sentinelRef} className="h-4 flex items-center justify-center">
          {loading && (
            <div className="flex items-center space-x-2">
              <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600"></div>
              <span className="text-sm text-gray-500">Loading more...</span>
            </div>
          )}
        </div>
      )}
    </div>
  )
}

Loading States & User Experience

Skeleton Components

Provide immediate visual feedback while content loads.

Comprehensive Skeleton System

// components/ui/skeleton.tsx
import { cn } from '@/lib/utils'

export function Skeleton({ 
  className, 
  ...props 
}: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      className={cn(
        "animate-pulse rounded-md bg-gray-200 dark:bg-gray-800",
        className
      )}
      {...props}
    />
  )
}

// Specific skeleton components
export function ProductTableSkeleton() {
  return (
    <div className="space-y-4">
      {/* Table header */}
      <div className="grid grid-cols-4 gap-4 pb-2 border-b">
        {Array.from({ length: 4 }).map((_, i) => (
          <Skeleton key={i} className="h-4 w-20" />
        ))}
      </div>
      
      {/* Table rows */}
      {Array.from({ length: 10 }).map((_, index) => (
        <div key={index} className="grid grid-cols-4 gap-4 py-3">
          <Skeleton className="h-4 w-32" />
          <Skeleton className="h-4 w-24" />
          <Skeleton className="h-4 w-16" />
          <Skeleton className="h-4 w-12" />
        </div>
      ))}
    </div>
  )
}

export function DashboardSkeleton() {
  return (
    <div className="space-y-6">
      {/* Stats cards */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        {Array.from({ length: 4 }).map((_, i) => (
          <div key={i} className="p-6 border rounded-lg">
            <div className="flex items-center justify-between">
              <Skeleton className="h-4 w-20" />
              <Skeleton className="h-6 w-6 rounded" />
            </div>
            <Skeleton className="h-8 w-16 mt-2" />
            <Skeleton className="h-3 w-24 mt-1" />
          </div>
        ))}
      </div>
      
      {/* Chart area */}
      <div className="p-6 border rounded-lg">
        <Skeleton className="h-6 w-40 mb-4" />
        <Skeleton className="h-64 w-full" />
      </div>
    </div>
  )
}

Smart Loading States

Implement intelligent loading states that improve perceived performance.

Loading State Manager

// hooks/use-loading-state.ts
import { useState, useCallback } from 'react'

interface LoadingState {
  loading: boolean
  error: string | null
  success: boolean
}

export function useLoadingState(initialState: Partial<LoadingState> = {}) {
  const [state, setState] = useState<LoadingState>({
    loading: false,
    error: null,
    success: false,
    ...initialState
  })

  const startLoading = useCallback(() => {
    setState({ loading: true, error: null, success: false })
  }, [])

  const setSuccess = useCallback(() => {
    setState({ loading: false, error: null, success: true })
  }, [])

  const setError = useCallback((error: string) => {
    setState({ loading: false, error, success: false })
  }, [])

  const reset = useCallback(() => {
    setState({ loading: false, error: null, success: false })
  }, [])

  const withLoading = useCallback(async <T>(
    promise: Promise<T>
  ): Promise<T> => {
    startLoading()
    try {
      const result = await promise
      setSuccess()
      return result
    } catch (error) {
      setError(error instanceof Error ? error.message : 'An error occurred')
      throw error
    }
  }, [startLoading, setSuccess, setError])

  return {
    ...state,
    startLoading,
    setSuccess,
    setError,
    reset,
    withLoading
  }
}

Frontend performance optimization requires continuous monitoring and improvement. These techniques provide a solid foundation for delivering fast, responsive user experiences in Smart Shelf.