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
Related Documentation
- Production Deployment - Deployment optimization
- Monitoring & Logging - Performance monitoring setup
- Environment Configuration - Performance settings
- Security Best Practices - Security with performance