Skip to main content

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)&lt;200'], // 95% of requests under 200ms
http_req_failed: ['rate&lt;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.