Performance Optimization Guide
Overviewβ
This guide provides comprehensive performance optimization strategies for both toto-app and toto-bo applications to meet production performance standards.
1. Frontend Performance Optimizationβ
Core Web Vitals Optimizationβ
Largest Contentful Paint (LCP) - Target: < 2.5sβ
// src/components/optimized/ImageOptimizer.tsx
import Image from 'next/image';
import { useState, useEffect } from 'react';
interface OptimizedImageProps {
src: string;
alt: string;
priority?: boolean;
className?: string;
width?: number;
height?: number;
}
export function OptimizedImage({
src,
alt,
priority = false,
className,
width = 800,
height = 600
}: OptimizedImageProps) {
const [isLoaded, setIsLoaded] = useState(false);
return (
<div className={`relative overflow-hidden ${className}`}>
<Image
src={src}
alt={alt}
width={width}
height={height}
priority={priority}
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
onLoad={() => setIsLoaded(true)}
className={`transition-opacity duration-300 ${
isLoaded ? 'opacity-100' : 'opacity-0'
}`}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
);
}
First Input Delay (FID) - Target: < 100msβ
// src/hooks/useOptimizedInteraction.ts
import { useCallback, useRef } from 'react';
export function useOptimizedInteraction() {
const timeoutRef = useRef<NodeJS.Timeout>();
const debounce = useCallback((fn: Function, delay: number) => {
return (...args: any[]) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => fn(...args), delay);
};
}, []);
const throttle = useCallback((fn: Function, delay: number) => {
let lastCall = 0;
return (...args: any[]) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
}
};
}, []);
return { debounce, throttle };
}
Cumulative Layout Shift (CLS) - Target: < 0.1β
// src/components/optimized/StableLayout.tsx
import { useEffect, useRef, useState } from 'react';
interface StableLayoutProps {
children: React.ReactNode;
minHeight?: number;
className?: string;
}
export function StableLayout({
children,
minHeight = 200,
className
}: StableLayoutProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [dimensions, setDimensions] = useState({ width: 0, height: minHeight });
useEffect(() => {
const updateDimensions = () => {
if (containerRef.current) {
const { offsetWidth, offsetHeight } = containerRef.current;
setDimensions({
width: offsetWidth,
height: Math.max(offsetHeight, minHeight)
});
}
};
updateDimensions();
window.addEventListener('resize', updateDimensions);
return () => window.removeEventListener('resize', updateDimensions);
}, [minHeight]);
return (
<div
ref={containerRef}
className={className}
style={{
minHeight: `${dimensions.height}px`,
width: dimensions.width > 0 ? `${dimensions.width}px` : '100%'
}}
>
{children}
</div>
);
}
Code Splitting & Lazy Loadingβ
Route-based Code Splittingβ
// src/pages/_app.tsx
import dynamic from 'next/dynamic';
import { Suspense } from 'react';
// Lazy load heavy components
const AnalyticsDashboard = dynamic(
() => import('@/components/dashboard/AnalyticsDashboard'),
{
loading: () => <div className="animate-pulse bg-gray-200 h-64 rounded" />,
ssr: false
}
);
const CaseManagement = dynamic(
() => import('@/components/cases/CaseManagement'),
{
loading: () => <div className="animate-pulse bg-gray-200 h-96 rounded" />
}
);
export default function App({ Component, pageProps }: AppProps) {
return (
<Suspense fallback={<div>Loading...</div>}>
<Component {...pageProps} />
</Suspense>
);
}
Component-level Lazy Loadingβ
// src/components/optimized/LazyComponent.tsx
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
const DataTable = lazy(() => import('./DataTable'));
interface LazyComponentProps {
type: 'chart' | 'table';
data: any[];
}
export function LazyComponent({ type, data }: LazyComponentProps) {
return (
<Suspense fallback={<ComponentSkeleton />}>
{type === 'chart' ? (
<HeavyChart data={data} />
) : (
<DataTable data={data} />
)}
</Suspense>
);
}
function ComponentSkeleton() {
return (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/2 mb-2"></div>
<div className="h-32 bg-gray-200 rounded"></div>
</div>
);
}
Bundle Optimizationβ
Webpack Bundle Analyzer Configurationβ
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
const nextConfig = {
// Bundle optimization
experimental: {
optimizeCss: true,
optimizePackageImports: ['lucide-react', '@radix-ui/react-icons']
},
// Tree shaking optimization
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
tls: false,
};
}
// Optimize bundle splitting
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true,
},
},
};
return config;
},
};
module.exports = withBundleAnalyzer(nextConfig);
Dynamic Imports for Heavy Librariesβ
// src/lib/optimized/chartLoader.ts
export async function loadChartLibrary() {
const { Chart } = await import('chart.js');
const { registerables } = await import('chart.js/auto');
Chart.register(...registerables);
return Chart;
}
// src/lib/optimized/mapLoader.ts
export async function loadMapLibrary() {
const { Map, Marker } = await import('react-leaflet');
const L = await import('leaflet');
return { Map, Marker, L };
}
2. Backend Performance Optimizationβ
API Response Optimizationβ
Response Caching Strategyβ
// src/lib/cache/redisCache.ts
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export class CacheService {
private static instance: CacheService;
private redis: Redis;
constructor() {
this.redis = redis;
}
static getInstance(): CacheService {
if (!CacheService.instance) {
CacheService.instance = new CacheService();
}
return CacheService.instance;
}
async get<T>(key: string): Promise<T | null> {
try {
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
} catch (error) {
console.error('Cache get error:', error);
return null;
}
}
async set(key: string, value: any, ttl: number = 3600): Promise<void> {
try {
await this.redis.setex(key, ttl, JSON.stringify(value));
} catch (error) {
console.error('Cache set error:', error);
}
}
async invalidate(pattern: string): Promise<void> {
try {
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
} catch (error) {
console.error('Cache invalidation error:', error);
}
}
}
export const cacheService = CacheService.getInstance();
Optimized API Endpointsβ
// src/pages/api/cases/optimized.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { cacheService } from '@/lib/cache/redisCache';
import { db } from '@/lib/firebase';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { method, query } = req;
if (method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
const cacheKey = `cases:${JSON.stringify(query)}`;
try {
// Try cache first
const cached = await cacheService.get(cacheKey);
if (cached) {
return res.status(200).json(cached);
}
// Build optimized query
let queryRef = db.collection('cases');
// Apply filters efficiently
if (query.status) {
queryRef = queryRef.where('status', '==', query.status);
}
if (query.animalType) {
queryRef = queryRef.where('animalType', '==', query.animalType);
}
if (query.location) {
queryRef = queryRef.where('location', '>=', query.location)
.where('location', '<=', query.location + '\uf8ff');
}
// Limit and pagination
const limit = Math.min(parseInt(query.limit as string) || 20, 100);
queryRef = queryRef.limit(limit);
if (query.cursor) {
queryRef = queryRef.startAfter(query.cursor);
}
// Execute query
const snapshot = await queryRef.get();
const cases = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// Cache for 5 minutes
await cacheService.set(cacheKey, cases, 300);
res.status(200).json({
cases,
nextCursor: snapshot.docs[snapshot.docs.length - 1]?.id || null
});
} catch (error) {
console.error('API Error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
Database Query Optimizationβ
Firestore Query Optimizationβ
// src/lib/database/optimizedQueries.ts
import { db } from '@/lib/firebase';
export class OptimizedQueries {
// Use composite indexes for complex queries
static async getCasesByLocationAndType(
location: string,
animalType: string,
limit: number = 20
) {
return db.collection('cases')
.where('location', '==', location)
.where('animalType', '==', animalType)
.where('status', '==', 'active')
.orderBy('createdAt', 'desc')
.limit(limit)
.get();
}
// Batch operations for better performance
static async batchUpdateCases(caseUpdates: Array<{id: string, data: any}>) {
const batch = db.batch();
caseUpdates.forEach(({ id, data }) => {
const ref = db.collection('cases').doc(id);
batch.update(ref, data);
});
return batch.commit();
}
// Use transactions for data consistency
static async transferCaseOwnership(
caseId: string,
fromUserId: string,
toUserId: string
) {
return db.runTransaction(async (transaction) => {
const caseRef = db.collection('cases').doc(caseId);
const fromUserRef = db.collection('users').doc(fromUserId);
const toUserRef = db.collection('users').doc(toUserId);
const [caseDoc, fromUserDoc, toUserDoc] = await Promise.all([
transaction.get(caseRef),
transaction.get(fromUserRef),
transaction.get(toUserRef)
]);
if (!caseDoc.exists || !fromUserDoc.exists || !toUserDoc.exists) {
throw new Error('Document not found');
}
// Update case ownership
transaction.update(caseRef, {
guardianId: toUserId,
updatedAt: new Date()
});
// Update user case counts
transaction.update(fromUserRef, {
caseCount: fromUserDoc.data()!.caseCount - 1
});
transaction.update(toUserRef, {
caseCount: toUserDoc.data()!.caseCount + 1
});
});
}
}
3. Image and Asset Optimizationβ
Image Optimization Pipelineβ
// src/lib/optimization/imageOptimizer.ts
import sharp from 'sharp';
export class ImageOptimizer {
static async optimizeImage(
inputBuffer: Buffer,
options: {
width?: number;
height?: number;
quality?: number;
format?: 'webp' | 'avif' | 'jpeg' | 'png';
} = {}
): Promise<Buffer> {
const {
width = 800,
height = 600,
quality = 85,
format = 'webp'
} = options;
let pipeline = sharp(inputBuffer)
.resize(width, height, {
fit: 'cover',
position: 'center'
});
switch (format) {
case 'webp':
pipeline = pipeline.webp({ quality });
break;
case 'avif':
pipeline = pipeline.avif({ quality });
break;
case 'jpeg':
pipeline = pipeline.jpeg({ quality });
break;
case 'png':
pipeline = pipeline.png({ quality });
break;
}
return pipeline.toBuffer();
}
static async generateResponsiveImages(
inputBuffer: Buffer,
sizes: number[] = [320, 640, 800, 1200, 1600]
): Promise<{ [key: string]: Buffer }> {
const results: { [key: string]: Buffer } = {};
for (const size of sizes) {
const webp = await this.optimizeImage(inputBuffer, {
width: size,
format: 'webp'
});
const avif = await this.optimizeImage(inputBuffer, {
width: size,
format: 'avif'
});
results[`${size}w.webp`] = webp;
results[`${size}w.avif`] = avif;
}
return results;
}
}
CDN Configurationβ
// src/lib/cdn/cloudflare.ts
export class CDNService {
private static instance: CDNService;
private apiToken: string;
private zoneId: string;
constructor() {
this.apiToken = process.env.CLOUDFLARE_API_TOKEN!;
this.zoneId = process.env.CLOUDFLARE_ZONE_ID!;
}
static getInstance(): CDNService {
if (!CDNService.instance) {
CDNService.instance = new CDNService();
}
return CDNService.instance;
}
async purgeCache(urls: string[]): Promise<void> {
const response = await fetch(
`https://api.cloudflare.com/client/v4/zones/${this.zoneId}/purge_cache`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ files: urls })
}
);
if (!response.ok) {
throw new Error('Failed to purge cache');
}
}
async setCacheRules(rules: Array<{
url: string;
ttl: number;
browserTTL: number;
}>): Promise<void> {
// Implement cache rule setting
console.log('Setting cache rules:', rules);
}
}
4. Monitoring and Performance Trackingβ
Performance Monitoring Serviceβ
// src/lib/monitoring/performanceMonitor.ts
export class PerformanceMonitor {
private static instance: PerformanceMonitor;
private metrics: Map<string, number[]> = new Map();
static getInstance(): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
}
return PerformanceMonitor.instance;
}
measureAsync<T>(
name: string,
fn: () => Promise<T>
): Promise<T> {
const start = performance.now();
return fn().then(
(result) => {
const duration = performance.now() - start;
this.recordMetric(name, duration);
return result;
},
(error) => {
const duration = performance.now() - start;
this.recordMetric(`${name}_error`, duration);
throw error;
}
);
}
measureSync<T>(name: string, fn: () => T): T {
const start = performance.now();
try {
const result = fn();
const duration = performance.now() - start;
this.recordMetric(name, duration);
return result;
} catch (error) {
const duration = performance.now() - start;
this.recordMetric(`${name}_error`, duration);
throw error;
}
}
private recordMetric(name: string, value: number): void {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
const values = this.metrics.get(name)!;
values.push(value);
// Keep only last 100 measurements
if (values.length > 100) {
values.shift();
}
}
getMetrics(): Record<string, {
average: number;
min: number;
max: number;
count: number;
}> {
const result: Record<string, any> = {};
for (const [name, values] of this.metrics) {
if (values.length === 0) continue;
result[name] = {
average: values.reduce((a, b) => a + b, 0) / values.length,
min: Math.min(...values),
max: Math.max(...values),
count: values.length
};
}
return result;
}
}
export const performanceMonitor = PerformanceMonitor.getInstance();
Real User Monitoring (RUM)β
// src/lib/monitoring/rum.ts
export class RealUserMonitoring {
static init(): void {
if (typeof window === 'undefined') return;
// Core Web Vitals
this.measureWebVitals();
// Custom metrics
this.measurePageLoad();
this.measureUserInteractions();
}
private static measureWebVitals(): void {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(this.sendToAnalytics);
getFID(this.sendToAnalytics);
getFCP(this.sendToAnalytics);
getLCP(this.sendToAnalytics);
getTTFB(this.sendToAnalytics);
});
}
private static measurePageLoad(): void {
window.addEventListener('load', () => {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
this.sendToAnalytics({
name: 'page_load',
value: navigation.loadEventEnd - navigation.loadEventStart,
delta: navigation.loadEventEnd - navigation.loadEventStart
});
});
}
private static measureUserInteractions(): void {
let interactionCount = 0;
['click', 'keydown', 'scroll'].forEach(eventType => {
document.addEventListener(eventType, () => {
interactionCount++;
if (interactionCount === 1) {
this.sendToAnalytics({
name: 'first_interaction',
value: performance.now(),
delta: performance.now()
});
}
}, { once: true });
});
}
private static sendToAnalytics(metric: any): void {
// Send to your analytics service
fetch('/api/analytics/metrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...metric,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
})
}).catch(console.error);
}
}
5. Performance Testingβ
Load Testing Configurationβ
// tests/performance/loadTest.ts
import { check, sleep } from 'k6';
import http from 'k6/http';
export let options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp up
{ duration: '5m', target: 100 }, // Stay at 100 users
{ duration: '2m', target: 200 }, // Ramp up to 200 users
{ duration: '5m', target: 200 }, // Stay at 200 users
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<200'], // 95% of requests under 200ms
http_req_failed: ['rate<0.01'], // Error rate under 1%
},
};
export default function() {
// Test cases endpoint
let response = http.get('https://app.betoto.pet/api/cases');
check(response, {
'cases endpoint status is 200': (r) => r.status === 200,
'cases endpoint response time < 200ms': (r) => r.timings.duration < 200,
});
sleep(1);
// Test case creation
let payload = JSON.stringify({
title: 'Test Case',
description: 'Load test case',
location: 'Test Location',
animalType: 'dog',
urgency: 'medium'
});
let params = {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer test-token'
},
};
response = http.post('https://app.betoto.pet/api/cases', payload, params);
check(response, {
'case creation status is 201': (r) => r.status === 201,
'case creation response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
6. Performance Optimization Checklistβ
Frontend Optimization Checklistβ
-
Core Web Vitals
- LCP < 2.5s
- FID < 100ms
- CLS < 0.1
- FCP < 1.8s
-
Code Splitting
- Route-based splitting implemented
- Component lazy loading
- Dynamic imports for heavy libraries
- Bundle size < 250KB gzipped
-
Image Optimization
- WebP/AVIF format support
- Responsive images
- Lazy loading implemented
- Image compression optimized
-
Caching Strategy
- Browser caching configured
- CDN caching enabled
- Service worker implemented
- Cache invalidation strategy
Backend Optimization Checklistβ
-
API Performance
- Response time < 200ms (95th percentile)
- Database queries optimized
- Caching implemented
- Rate limiting configured
-
Database Optimization
- Indexes created for queries
- Query patterns optimized
- Connection pooling configured
- Batch operations implemented
-
Infrastructure
- Auto-scaling configured
- Load balancing implemented
- CDN configured
- Monitoring and alerting
Performance Monitoring Checklistβ
-
Real User Monitoring
- Core Web Vitals tracking
- Custom metrics collection
- Error tracking
- User journey analysis
-
Synthetic Monitoring
- Uptime monitoring
- Performance testing
- Load testing
- Stress testing
This performance optimization guide ensures both toto-app and toto-bo meet production performance standards and provide excellent user experience.