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.