Performance & Optimization
Performance optimization, monitoring, and scalability strategies for Smart Shelf deployment.
Performance & Optimization
This section covers performance optimization strategies, monitoring techniques, and scalability approaches for Smart Shelf in production.
Build Optimization
Next.js Configuration
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
// Build optimization
swcMinify: true,
compress: true,
// Bundle analysis
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
};
}
// Bundle analyzer in development
if (process.env.ANALYZE === 'true') {
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
config.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
})
);
}
return config;
},
// Image optimization
images: {
domains: ['supabase-storage-url.com'],
formats: ['image/webp', 'image/avif'],
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
},
// Headers for caching
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'Cache-Control', value: 'no-store, must-revalidate' },
],
},
{
source: '/_next/static/:path*',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' },
],
},
];
},
// Experimental features
experimental: {
serverComponentsExternalPackages: ['@supabase/supabase-js'],
optimizePackageImports: ['lucide-react', '@radix-ui/react-icons'],
},
};
export default nextConfig;
Bundle Analysis and Optimization
// scripts/analyze-bundle.ts
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
async function analyzeBundles() {
console.log('📊 Analyzing bundle sizes...');
try {
// Build with bundle analyzer
await execAsync('ANALYZE=true npm run build');
// Check bundle sizes
const { stdout } = await execAsync('npx next-bundle-analyzer');
console.log('Bundle analysis complete:', stdout);
// Check for large bundles
const largeBundles = checkBundleSizes();
if (largeBundles.length > 0) {
console.warn('⚠️ Large bundles detected:', largeBundles);
}
} catch (error) {
console.error('Bundle analysis failed:', error);
process.exit(1);
}
}
function checkBundleSizes() {
// Implementation to check bundle sizes and warn about large bundles
const MAX_BUNDLE_SIZE = 1024 * 1024; // 1MB
const largeBundles: string[] = [];
// Check each bundle against threshold
// Return array of bundles exceeding limit
return largeBundles;
}
if (require.main === module) {
analyzeBundles();
}
Database Performance
Query Optimization
-- Performance indexes for common queries
CREATE INDEX CONCURRENTLY idx_inventory_product_warehouse
ON inventory(product_id, warehouse_id);
CREATE INDEX CONCURRENTLY idx_stock_movements_product_date
ON stock_movements(product_id, created_at DESC);
CREATE INDEX CONCURRENTLY idx_products_search
ON products USING gin(to_tsvector('english', name || ' ' || description));
-- Partial indexes for active records
CREATE INDEX CONCURRENTLY idx_products_active
ON products(name) WHERE is_active = true;
CREATE INDEX CONCURRENTLY idx_inventory_available
ON inventory(product_id, quantity_available) WHERE quantity_available > 0;
-- Composite indexes for complex queries
CREATE INDEX CONCURRENTLY idx_orders_status_date
ON sales_orders(status, created_at DESC);
-- Enable query plan caching
ALTER DATABASE smart_shelf SET shared_preload_libraries = 'pg_stat_statements';
ALTER DATABASE smart_shelf SET pg_stat_statements.track = 'all';
Connection Pool Optimization
// lib/supabase/performance.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
// Optimized connection configuration
export const createOptimizedClient = () => {
return createClient(supabaseUrl, supabaseKey, {
db: {
schema: 'public',
},
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
global: {
headers: {
'x-application-name': 'smart-shelf',
},
},
// Connection pooling settings
realtime: {
params: {
eventsPerSecond: 10,
},
},
});
};
// Query performance tracking
export async function trackQuery<T>(
queryName: string,
queryFn: () => Promise<T>
): Promise<T> {
const start = performance.now();
try {
const result = await queryFn();
const duration = performance.now() - start;
// Log slow queries (> 1 second)
if (duration > 1000) {
console.warn(`🐌 Slow query detected: ${queryName} took ${duration.toFixed(2)}ms`);
}
// Track metrics in production
if (process.env.NODE_ENV === 'production') {
await logPerformanceMetric({
type: 'database_query',
name: queryName,
duration,
timestamp: new Date(),
});
}
return result;
} catch (error) {
const duration = performance.now() - start;
console.error(`❌ Query failed: ${queryName} after ${duration.toFixed(2)}ms`, error);
throw error;
}
}
Performance Monitoring
Real User Monitoring (RUM)
// lib/monitoring/performance.ts
import { ENV } from '@/lib/config/environment';
// Performance monitoring
export function trackPerformance(name: string, fn: () => Promise<any>) {
return async function(...args: any[]) {
const start = performance.now();
try {
const result = await fn.apply(this, args);
const duration = performance.now() - start;
// Log performance metrics
if (ENV.NODE_ENV === 'production') {
await fetch('/api/metrics/performance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
duration,
timestamp: new Date().toISOString(),
}),
});
}
return result;
} catch (error) {
// Track errors
if (ENV.NODE_ENV === 'production') {
await fetch('/api/metrics/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
}),
});
}
throw error;
}
};
}
// Real User Monitoring (RUM)
export function initRUM() {
if (typeof window !== 'undefined' && ENV.NODE_ENV === 'production') {
// Track Core Web Vitals
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(sendMetric);
getFID(sendMetric);
getFCP(sendMetric);
getLCP(sendMetric);
getTTFB(sendMetric);
});
}
}
function sendMetric(metric: any) {
fetch('/api/metrics/web-vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metric),
});
}
// Performance budget monitoring
export const PERFORMANCE_BUDGETS = {
FCP: 1.8, // First Contentful Paint
LCP: 2.5, // Largest Contentful Paint
FID: 0.1, // First Input Delay
CLS: 0.1, // Cumulative Layout Shift
TTFB: 0.8, // Time to First Byte
};
export function checkPerformanceBudgets(metrics: Record<string, number>) {
const violations = [];
for (const [metric, value] of Object.entries(metrics)) {
const budget = PERFORMANCE_BUDGETS[metric as keyof typeof PERFORMANCE_BUDGETS];
if (budget && value > budget) {
violations.push({
metric,
value,
budget,
exceeded: value - budget,
});
}
}
return violations;
}
Health Checks and Monitoring
// app/api/health/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createServiceRoleClient } from '@/lib/supabase/server';
export async function GET(request: NextRequest) {
const checks = [];
// Database health check
try {
const supabase = createServiceRoleClient();
const start = performance.now();
const { data, error } = await supabase
.from('health_check')
.select('count')
.limit(1);
const responseTime = performance.now() - start;
checks.push({
name: 'database',
status: error ? 'unhealthy' : 'healthy',
responseTime: Math.round(responseTime),
details: error ? { error: error.message } : null,
});
} catch (error) {
checks.push({
name: 'database',
status: 'unhealthy',
error: error.message,
});
}
// External services health check
try {
const start = performance.now();
const response = await fetch('https://api.stripe.com/v1/account', {
headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}` },
});
const responseTime = performance.now() - start;
checks.push({
name: 'stripe',
status: response.ok ? 'healthy' : 'unhealthy',
responseTime: Math.round(responseTime),
});
} catch (error) {
checks.push({
name: 'stripe',
status: 'unhealthy',
error: error.message,
});
}
// Memory usage check
if (process.memoryUsage) {
const memUsage = process.memoryUsage();
checks.push({
name: 'memory',
status: memUsage.heapUsed < 512 * 1024 * 1024 ? 'healthy' : 'warning', // 512MB threshold
details: {
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
external: Math.round(memUsage.external / 1024 / 1024),
},
});
}
const allHealthy = checks.every(check => check.status === 'healthy');
return NextResponse.json(
{
status: allHealthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
version: process.env.NEXT_PUBLIC_APP_VERSION,
checks,
},
{ status: allHealthy ? 200 : 503 }
);
}
Caching Strategies
Application-Level Caching
// lib/cache/redis.ts
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export interface CacheOptions {
ttl?: number; // Time to live in seconds
prefix?: string;
}
export class CacheManager {
private defaultTTL = 3600; // 1 hour
async get<T>(key: string, options?: CacheOptions): Promise<T | null> {
try {
const fullKey = this.buildKey(key, options?.prefix);
const value = await redis.get(fullKey);
return value ? JSON.parse(value) : null;
} catch (error) {
console.error('Cache get error:', error);
return null;
}
}
async set<T>(key: string, value: T, options?: CacheOptions): Promise<void> {
try {
const fullKey = this.buildKey(key, options?.prefix);
const ttl = options?.ttl || this.defaultTTL;
await redis.setex(fullKey, ttl, JSON.stringify(value));
} catch (error) {
console.error('Cache set error:', error);
}
}
async del(key: string, options?: CacheOptions): Promise<void> {
try {
const fullKey = this.buildKey(key, options?.prefix);
await redis.del(fullKey);
} catch (error) {
console.error('Cache delete error:', error);
}
}
async invalidatePattern(pattern: string): Promise<void> {
try {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
} catch (error) {
console.error('Cache invalidation error:', error);
}
}
private buildKey(key: string, prefix?: string): string {
return prefix ? `${prefix}:${key}` : key;
}
}
export const cache = new CacheManager();
// Cache decorator for functions
export function cached<T extends (...args: any[]) => Promise<any>>(
fn: T,
options?: CacheOptions & { keyGenerator?: (...args: Parameters<T>) => string }
): T {
return (async (...args: Parameters<T>) => {
const key = options?.keyGenerator
? options.keyGenerator(...args)
: `${fn.name}:${JSON.stringify(args)}`;
// Try to get from cache
const cached = await cache.get(key, options);
if (cached) return cached;
// Execute function and cache result
const result = await fn(...args);
await cache.set(key, result, options);
return result;
}) as T;
}
Database Query Caching
// lib/cache/query-cache.ts
import { cache } from './redis';
export function cacheQuery<T>(
queryName: string,
ttl: number = 300 // 5 minutes default
) {
return function(queryFn: () => Promise<T>): Promise<T> {
return cached(queryFn, {
prefix: 'query',
ttl,
keyGenerator: () => queryName,
})();
};
}
// Usage example
export const getProductsWithCache = () =>
cacheQuery('products:active', 600)(async () => {
const supabase = createOptimizedClient();
const { data } = await supabase
.from('products')
.select('*')
.eq('is_active', true);
return data;
});
Image and Asset Optimization
Image Processing Pipeline
// lib/image/optimization.ts
import sharp from 'sharp';
export interface ImageOptimizationOptions {
width?: number;
height?: number;
quality?: number;
format?: 'webp' | 'avif' | 'jpeg' | 'png';
}
export async function optimizeImage(
input: Buffer,
options: ImageOptimizationOptions = {}
): Promise<Buffer> {
const {
width,
height,
quality = 80,
format = 'webp',
} = options;
let pipeline = sharp(input);
// Resize if dimensions provided
if (width || height) {
pipeline = pipeline.resize(width, height, {
fit: 'cover',
position: 'center',
});
}
// Apply format-specific optimizations
switch (format) {
case 'webp':
pipeline = pipeline.webp({ quality });
break;
case 'avif':
pipeline = pipeline.avif({ quality });
break;
case 'jpeg':
pipeline = pipeline.jpeg({ quality, progressive: true });
break;
case 'png':
pipeline = pipeline.png({ compressionLevel: 9 });
break;
}
return pipeline.toBuffer();
}
// Image variants generation
export async function generateImageVariants(
originalBuffer: Buffer,
variants: ImageOptimizationOptions[]
): Promise<Record<string, Buffer>> {
const results: Record<string, Buffer> = {};
await Promise.all(
variants.map(async (variant, index) => {
const key = `variant-${index}`;
results[key] = await optimizeImage(originalBuffer, variant);
})
);
return results;
}
Performance Best Practices
Code Splitting
// components/LazyComponents.tsx
import { lazy, Suspense } from 'react';
import { Skeleton } from '@/components/ui/skeleton';
// Lazy load heavy components
const ProductCatalog = lazy(() => import('./ProductCatalog'));
const InventoryDashboard = lazy(() => import('./InventoryDashboard'));
const ReportsPanel = lazy(() => import('./ReportsPanel'));
export function LazyProductCatalog() {
return (
<Suspense fallback={<ProductCatalogSkeleton />}>
<ProductCatalog />
</Suspense>
);
}
export function LazyInventoryDashboard() {
return (
<Suspense fallback={<DashboardSkeleton />}>
<InventoryDashboard />
</Suspense>
);
}
function ProductCatalogSkeleton() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} className="h-48 w-full" />
))}
</div>
);
}
Resource Loading
// lib/performance/resource-hints.ts
export function preloadCriticalResources() {
if (typeof window === 'undefined') return;
// Preload critical fonts
const fontLink = document.createElement('link');
fontLink.rel = 'preload';
fontLink.href = '/fonts/inter-var.woff2';
fontLink.as = 'font';
fontLink.type = 'font/woff2';
fontLink.crossOrigin = 'anonymous';
document.head.appendChild(fontLink);
// Prefetch likely next pages
const prefetchLinks = [
'/dashboard',
'/products',
'/inventory',
];
prefetchLinks.forEach(href => {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = href;
document.head.appendChild(link);
});
}
export function preconnectToExternalDomains() {
const domains = [
'https://fonts.googleapis.com',
'https://cdn.jsdelivr.net',
process.env.NEXT_PUBLIC_SUPABASE_URL,
];
domains.forEach(domain => {
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = domain;
document.head.appendChild(link);
});
}
Performance Checklist
Development
- Bundle analysis configured
- Code splitting implemented
- Lazy loading for heavy components
- Image optimization enabled
- Caching strategies in place
Database
- Proper indexes created
- Query performance monitored
- Connection pooling optimized
- Slow query logging enabled
Monitoring
- Core Web Vitals tracked
- Performance budgets defined
- Health checks implemented
- Real user monitoring active
- Error tracking configured
This performance optimization strategy ensures Smart Shelf delivers excellent user experience with fast loading times and responsive interactions.