Episode 20: Performance Optimization & Monitoring
Building Lightning-Fast, Scalable Portal Experiences
Introduction
Performance is critical for user adoption and business success. This comprehensive episode covers advanced performance optimization techniques including intelligent caching strategies, image optimization, code splitting, real-time monitoring, and scalability planning. You’ll learn to build portals that deliver exceptional performance across all devices and user scenarios while maintaining comprehensive monitoring for proactive issue detection.
Key Concepts
- Performance Metrics: Understanding Core Web Vitals, load times, and user experience indicators
- Caching Strategies: Implementing multi-level caching for optimal performance
- Asset Optimization: Optimizing images, scripts, and styles for fast delivery
- Real-Time Monitoring: Comprehensive performance tracking and alerting systems
- Scalability Planning: Building portals that perform well under increasing load
Learning Objectives
By the end of this episode, you will be able to:
- Implement comprehensive performance optimization strategies across all portal components
- Design and deploy advanced caching architectures for optimal performance
- Build real-time monitoring systems for proactive performance management
- Optimize images, scripts, and content delivery for maximum speed
- Plan and implement scalability solutions that grow with your business needs
Why Performance Optimization Matters
Portal performance directly impacts user satisfaction, conversion rates, search engine rankings, and business outcomes. Slow portals lose users, reduce engagement, and damage brand reputation. Advanced performance optimization ensures your portal provides exceptional experiences that keep users engaged and drive business success.
Step-by-Step Guide
1. Advanced Caching Architecture Implementation
Multi-Level Caching System:
// Advanced Caching Manager
class AdvancedCacheManager {
constructor(config) {
this.config = config;
this.cacheStrategies = {
browser: new BrowserCacheStrategy(),
cdn: new CDNCacheStrategy(),
application: new ApplicationCacheStrategy(),
database: new DatabaseCacheStrategy()
};
this.performanceMonitor = new PerformanceMonitor();
this.invalidationQueue = new InvalidationQueue();
}
// Intelligent cache retrieval with fallback strategy
async get(key, options = {}) {
const cacheKey = this.generateCacheKey(key, options);
const strategy = options.strategy || this.determineBestStrategy(key, options);
try {
// Start performance measurement
const startTime = performance.now();
// Try cache layers in order of speed
const cacheOrder = this.getCacheOrder(strategy);
for (const cacheLevel of cacheOrder) {
const result = await this.cacheStrategies[cacheLevel].get(cacheKey);
if (result) {
// Record cache hit
const duration = performance.now() - startTime;
await this.performanceMonitor.recordCacheHit(cacheLevel, duration);
// Populate higher-speed caches if hit was from slower cache
if (cacheOrder.indexOf(cacheLevel) > 0) {
await this.populateUpstreamCaches(cacheKey, result, cacheOrder, cacheLevel);
}
return {
data: result.data,
metadata: {
cacheLevel: cacheLevel,
timestamp: result.timestamp,
ttl: result.ttl,
performance: { duration }
}
};
}
}
// Cache miss - record and return null
const duration = performance.now() - startTime;
await this.performanceMonitor.recordCacheMiss(cacheKey, duration);
return null;
} catch (error) {
await this.performanceMonitor.recordCacheError(cacheKey, error);
throw new CacheError(`Cache retrieval failed: ${error.message}`);
}
}
// Intelligent cache storage with optimal TTL
async set(key, data, options = {}) {
const cacheKey = this.generateCacheKey(key, options);
const strategy = options.strategy || this.determineBestStrategy(key, options);
const ttl = options.ttl || this.calculateOptimalTTL(key, data, options);
try {
const startTime = performance.now();
// Prepare cache entry
const cacheEntry = {
data: data,
timestamp: Date.now(),
ttl: ttl,
metadata: {
size: this.calculateDataSize(data),
compression: options.compression || 'gzip',
tags: options.tags || []
}
};
// Compress data if beneficial
if (cacheEntry.metadata.size > this.config.compressionThreshold) {
cacheEntry.data = await this.compressData(data, cacheEntry.metadata.compression);
cacheEntry.metadata.compressed = true;
}
// Store in appropriate cache levels
const cacheOrder = this.getCacheOrder(strategy);
const storagePromises = cacheOrder.map(async (cacheLevel) => {
const levelConfig = this.config.levels[cacheLevel];
const adjustedTTL = this.adjustTTLForLevel(ttl, cacheLevel);
return this.cacheStrategies[cacheLevel].set(
cacheKey,
cacheEntry,
adjustedTTL,
levelConfig
);
});
await Promise.allSettled(storagePromises);
// Record performance
const duration = performance.now() - startTime;
await this.performanceMonitor.recordCacheWrite(cacheKey, duration, cacheEntry.metadata.size);
// Schedule invalidation if needed
if (options.invalidateAfter) {
await this.scheduleInvalidation(cacheKey, options.invalidateAfter);
}
return {
success: true,
cacheKey: cacheKey,
ttl: ttl,
performance: { duration }
};
} catch (error) {
await this.performanceMonitor.recordCacheError(cacheKey, error);
throw new CacheError(`Cache storage failed: ${error.message}`);
}
}
// Smart cache invalidation with dependency tracking
async invalidate(pattern, options = {}) {
try {
const startTime = performance.now();
// Resolve cache keys to invalidate
const keysToInvalidate = await this.resolveCacheKeys(pattern, options);
if (keysToInvalidate.length === 0) {
return { invalidated: 0 };
}
// Batch invalidation for efficiency
const batchSize = this.config.invalidationBatchSize || 100;
const batches = this.createBatches(keysToInvalidate, batchSize);
let totalInvalidated = 0;
for (const batch of batches) {
const invalidationPromises = Object.keys(this.cacheStrategies).map(async (cacheLevel) => {
return this.cacheStrategies[cacheLevel].invalidateBatch(batch);
});
const results = await Promise.allSettled(invalidationPromises);
totalInvalidated += batch.length;
// Log any invalidation failures
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.error(`Invalidation failed for ${Object.keys(this.cacheStrategies)[index]}:`, result.reason);
}
});
}
// Record performance
const duration = performance.now() - startTime;
await this.performanceMonitor.recordCacheInvalidation(pattern, totalInvalidated, duration);
// Handle dependent cache invalidation
if (options.cascadeInvalidation) {
await this.handleDependentInvalidation(keysToInvalidate);
}
return {
invalidated: totalInvalidated,
performance: { duration }
};
} catch (error) {
await this.performanceMonitor.recordCacheError(pattern, error);
throw new CacheError(`Cache invalidation failed: ${error.message}`);
}
}
// Determine optimal caching strategy based on data characteristics
determineBestStrategy(key, options) {
const dataType = options.dataType || this.inferDataType(key);
const accessPattern = options.accessPattern || this.analyzeAccessPattern(key);
const updateFrequency = options.updateFrequency || this.getUpdateFrequency(key);
// High-frequency read, low-frequency write: Multi-level with long TTL
if (accessPattern.reads > 100 && updateFrequency < 0.1) {
return 'aggressive';
}
// Frequently updated: Browser cache only with short TTL
if (updateFrequency > 10) {
return 'conservative';
}
// User-specific data: Application cache with medium TTL
if (dataType === 'user_specific') {
return 'user_focused';
}
// Static content: Full CDN caching
if (dataType === 'static') {
return 'static_content';
}
return 'balanced';
}
// Calculate optimal TTL based on data characteristics and usage patterns
calculateOptimalTTL(key, data, options) {
const baselineMetrics = this.getBaselineTTL(options.dataType);
const usagePattern = this.analyzeUsagePattern(key);
const dataVolatility = this.assessDataVolatility(data, options);
let optimalTTL = baselineMetrics.ttl;
// Adjust based on access frequency
if (usagePattern.accessFrequency > 50) {
optimalTTL *= 1.5; // Cache longer for frequently accessed data
}
// Adjust based on data volatility
if (dataVolatility > 0.8) {
optimalTTL *= 0.5; // Shorter cache for volatile data
}
// Adjust based on data size
const dataSize = this.calculateDataSize(data);
if (dataSize > this.config.largeCacheThreshold) {
optimalTTL *= 1.2; // Cache larger items longer to amortize storage cost
}
// Respect configured limits
return Math.max(
this.config.minTTL,
Math.min(optimalTTL, this.config.maxTTL)
);
}
}
// Browser Cache Strategy Implementation
class BrowserCacheStrategy {
constructor() {
this.storage = {
memory: new Map(),
sessionStorage: window.sessionStorage,
localStorage: window.localStorage,
indexedDB: null
};
this.initializeIndexedDB();
}
async initializeIndexedDB() {
if ('indexedDB' in window) {
try {
this.storage.indexedDB = await this.openIndexedDB();
} catch (error) {
console.warn('IndexedDB initialization failed:', error);
}
}
}
async get(key) {
// Try memory cache first (fastest)
if (this.storage.memory.has(key)) {
const entry = this.storage.memory.get(key);
if (Date.now() < entry.expiry) {
return entry;
} else {
this.storage.memory.delete(key);
}
}
// Try IndexedDB (larger capacity)
if (this.storage.indexedDB) {
try {
const entry = await this.getFromIndexedDB(key);
if (entry && Date.now() < entry.expiry) {
// Populate memory cache for faster subsequent access
this.storage.memory.set(key, entry);
return entry;
}
} catch (error) {
console.warn('IndexedDB read error:', error);
}
}
// Try localStorage (persistent)
try {
const stored = this.storage.localStorage.getItem(key);
if (stored) {
const entry = JSON.parse(stored);
if (Date.now() < entry.expiry) {
this.storage.memory.set(key, entry);
return entry;
} else {
this.storage.localStorage.removeItem(key);
}
}
} catch (error) {
console.warn('localStorage read error:', error);
}
return null;
}
async set(key, data, ttl, config = {}) {
const expiry = Date.now() + (ttl * 1000);
const entry = {
data: data,
timestamp: Date.now(),
expiry: expiry,
ttl: ttl
};
// Always store in memory for fastest access
this.storage.memory.set(key, entry);
// Manage memory cache size
if (this.storage.memory.size > 1000) {
this.evictOldestEntries(100);
}
// Store in IndexedDB for larger items
const dataSize = this.calculateSize(data);
if (dataSize > 1024 && this.storage.indexedDB) {
try {
await this.setInIndexedDB(key, entry);
} catch (error) {
console.warn('IndexedDB write error:', error);
}
}
// Store in localStorage for persistence (smaller items only)
if (dataSize < 5120) { // 5KB limit for localStorage
try {
this.storage.localStorage.setItem(key, JSON.stringify(entry));
} catch (error) {
if (error.name === 'QuotaExceededError') {
this.cleanupLocalStorage();
try {
this.storage.localStorage.setItem(key, JSON.stringify(entry));
} catch (retryError) {
console.warn('localStorage write failed after cleanup:', retryError);
}
}
}
}
return true;
}
evictOldestEntries(count) {
const entries = Array.from(this.storage.memory.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp);
for (let i = 0; i < count && i < entries.length; i++) {
this.storage.memory.delete(entries[i][0]);
}
}
calculateSize(data) {
return JSON.stringify(data).length;
}
}
CDN Cache Integration:
// CDN Cache Strategy with Edge Computing
class CDNCacheStrategy {
constructor(config) {
this.config = config;
this.edgeLocations = config.edgeLocations || [];
this.distributionDomain = config.distributionDomain;
this.performanceMonitor = new CDNPerformanceMonitor();
}
async get(key) {
const cacheUrl = this.buildCacheUrl(key);
try {
const startTime = performance.now();
// Use fetch with cache-first strategy
const response = await fetch(cacheUrl, {
method: 'GET',
headers: {
'Cache-Control': 'max-age=3600',
'X-Cache-Key': key
},
cache: 'force-cache'
});
if (response.ok) {
const data = await response.json();
const duration = performance.now() - startTime;
// Record CDN performance
await this.performanceMonitor.recordCDNHit(key, duration, response.headers);
return {
data: data,
timestamp: Date.now(),
source: 'cdn',
edge: response.headers.get('X-Edge-Location'),
cacheStatus: response.headers.get('X-Cache-Status')
};
}
return null;
} catch (error) {
await this.performanceMonitor.recordCDNError(key, error);
return null;
}
}
async set(key, data, ttl, config = {}) {
const cacheUrl = this.buildCacheUrl(key);
try {
const startTime = performance.now();
// Store in CDN with appropriate headers
const response = await fetch(cacheUrl, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Cache-Control': `max-age=${ttl}`,
'X-Cache-TTL': ttl.toString(),
'X-Cache-Tags': (config.tags || []).join(',')
},
body: JSON.stringify(data)
});
const duration = performance.now() - startTime;
if (response.ok) {
await this.performanceMonitor.recordCDNWrite(key, duration, JSON.stringify(data).length);
return true;
}
return false;
} catch (error) {
await this.performanceMonitor.recordCDNError(key, error);
return false;
}
}
// Intelligent cache warming
async warmCache(keys, priority = 'normal') {
const warmingPromises = keys.map(async (key) => {
try {
// Pre-fetch content to edge locations
const warmingUrl = this.buildWarmingUrl(key, priority);
const response = await fetch(warmingUrl, {
method: 'POST',
headers: {
'X-Cache-Warming': 'true',
'X-Priority': priority
}
});
return {
key: key,
success: response.ok,
edges: response.headers.get('X-Warmed-Edges')?.split(',') || []
};
} catch (error) {
return {
key: key,
success: false,
error: error.message
};
}
});
const results = await Promise.allSettled(warmingPromises);
return {
total: keys.length,
successful: results.filter(r => r.status === 'fulfilled' && r.value.success).length,
failed: results.filter(r => r.status === 'rejected' || !r.value.success).length,
details: results.map(r => r.status === 'fulfilled' ? r.value : { error: r.reason })
};
}
buildCacheUrl(key) {
return `https://${this.distributionDomain}/cache/${encodeURIComponent(key)}`;
}
buildWarmingUrl(key, priority) {
return `https://${this.distributionDomain}/warm/${encodeURIComponent(key)}?priority=${priority}`;
}
}
2. Image Optimization and Asset Management
Intelligent Image Optimization:
// Advanced Image Optimization Manager
class ImageOptimizationManager {
constructor(config) {
this.config = config;
this.supportedFormats = ['webp', 'avif', 'jpeg', 'png'];
this.deviceProfiles = this.initializeDeviceProfiles();
this.compressionWorker = new CompressionWorker();
}
// Intelligent image serving with format selection
async optimizeImageDelivery(imageUrl, context = {}) {
try {
const deviceProfile = this.getDeviceProfile(context);
const optimalFormat = this.selectOptimalFormat(context.supportedFormats, deviceProfile);
const optimalSize = this.calculateOptimalSize(deviceProfile, context.containerSize);
// Build optimized image URL
const optimizedUrl = this.buildOptimizedUrl(imageUrl, {
format: optimalFormat,
width: optimalSize.width,
height: optimalSize.height,
quality: this.getOptimalQuality(optimalFormat, deviceProfile),
compression: this.getCompressionSettings(optimalFormat, deviceProfile)
});
return {
url: optimizedUrl,
format: optimalFormat,
size: optimalSize,
estimatedSize: this.estimateFileSize(optimalSize, optimalFormat),
fallbackUrl: this.buildFallbackUrl(imageUrl, optimalSize)
};
} catch (error) {
console.error('Image optimization failed:', error);
return { url: imageUrl, fallback: true };
}
}
// Progressive image loading implementation
createProgressiveImage(container, imageOptions) {
const img = document.createElement('img');
const placeholder = this.createPlaceholder(imageOptions);
// Insert placeholder first
container.appendChild(placeholder);
// Create image loading strategy
const loadingStrategy = this.determineLoadingStrategy(imageOptions);
switch (loadingStrategy) {
case 'lazy':
this.implementLazyLoading(img, imageOptions, placeholder);
break;
case 'progressive':
this.implementProgressiveLoading(img, imageOptions, placeholder);
break;
case 'immediate':
this.loadImage(img, imageOptions, placeholder);
break;
}
return img;
}
// Lazy loading with Intersection Observer
implementLazyLoading(img, imageOptions, placeholder) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(img, imageOptions, placeholder);
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '50px' // Start loading 50px before image enters viewport
});
observer.observe(placeholder);
}
// Progressive loading with low-quality preview
implementProgressiveLoading(img, imageOptions, placeholder) {
// Load low-quality preview first
const previewOptions = {
...imageOptions,
quality: 20,
blur: 5
};
const preview = new Image();
preview.onload = () => {
placeholder.style.backgroundImage = `url(${preview.src})`;
placeholder.style.filter = 'blur(5px)';
// Load full-quality image
this.loadImage(img, imageOptions, placeholder);
};
preview.src = this.buildOptimizedUrl(imageOptions.src, previewOptions);
}
// Full image loading with error handling
loadImage(img, imageOptions, placeholder) {
const startTime = performance.now();
img.onload = () => {
const loadTime = performance.now() - startTime;
// Apply smooth transition
img.style.opacity = '0';
img.style.transition = 'opacity 0.3s ease-in-out';
placeholder.parentNode.replaceChild(img, placeholder);
// Fade in the image
requestAnimationFrame(() => {
img.style.opacity = '1';
});
// Record performance metrics
this.recordImageLoadMetrics(imageOptions.src, loadTime, img.naturalWidth, img.naturalHeight);
};
img.onerror = () => {
// Load fallback image
if (imageOptions.fallback) {
img.src = imageOptions.fallback;
} else {
placeholder.classList.add('image-error');
placeholder.innerHTML = '<span>Image unavailable</span>';
}
};
// Set up responsive source sets
if (imageOptions.responsive) {
this.setupResponsiveSources(img, imageOptions);
}
img.src = imageOptions.optimizedUrl;
img.alt = imageOptions.alt || '';
img.className = imageOptions.className || '';
}
// Create responsive source sets
setupResponsiveSources(img, imageOptions) {
const picture = document.createElement('picture');
// Add sources for different screen densities and sizes
const breakpoints = [
{ media: '(max-width: 768px)', width: 768 },
{ media: '(max-width: 1024px)', width: 1024 },
{ media: '(min-width: 1025px)', width: 1920 }
];
breakpoints.forEach(breakpoint => {
const source = document.createElement('source');
source.media = breakpoint.media;
// Generate srcset for different densities
const srcset = [1, 1.5, 2].map(density => {
const width = Math.round(breakpoint.width * density);
const url = this.buildOptimizedUrl(imageOptions.src, {
...imageOptions,
width: width
});
return `${url} ${density}x`;
}).join(', ');
source.srcset = srcset;
picture.appendChild(source);
});
picture.appendChild(img);
return picture;
}
selectOptimalFormat(supportedFormats, deviceProfile) {
// Check browser support and device capabilities
const browserSupported = supportedFormats || this.detectSupportedFormats();
// Prefer newer, more efficient formats
if (browserSupported.includes('avif') && deviceProfile.modernCodecs) {
return 'avif';
}
if (browserSupported.includes('webp')) {
return 'webp';
}
// Fallback to traditional formats
return deviceProfile.preferJPEG ? 'jpeg' : 'png';
}
calculateOptimalSize(deviceProfile, containerSize) {
const dpr = window.devicePixelRatio || 1;
const baseWidth = containerSize?.width || deviceProfile.defaultWidth;
const baseHeight = containerSize?.height || deviceProfile.defaultHeight;
// Account for device pixel ratio but cap at reasonable limits
const maxDPR = deviceProfile.highDensity ? 2 : 1.5;
const effectiveDPR = Math.min(dpr, maxDPR);
return {
width: Math.round(baseWidth * effectiveDPR),
height: Math.round(baseHeight * effectiveDPR),
dpr: effectiveDPR
};
}
getOptimalQuality(format, deviceProfile) {
const baseQuality = {
'avif': 75,
'webp': 80,
'jpeg': 85,
'png': 95
};
let quality = baseQuality[format] || 85;
// Adjust for device characteristics
if (deviceProfile.lowBandwidth) {
quality -= 15; // Lower quality for slow connections
}
if (deviceProfile.highDensity) {
quality -= 5; // Slight reduction for high-DPI displays
}
return Math.max(30, Math.min(95, quality));
}
}
3. Real-Time Performance Monitoring
Comprehensive Performance Monitoring System:
// Advanced Performance Monitor
class AdvancedPerformanceMonitor {
constructor(config) {
this.config = config;
this.metrics = new Map();
this.observers = new Map();
this.alertThresholds = config.alertThresholds || {};
this.reportingEndpoint = config.reportingEndpoint;
this.initializeMonitoring();
}
initializeMonitoring() {
// Core Web Vitals monitoring
this.setupCoreWebVitals();
// Custom performance metrics
this.setupCustomMetrics();
// Resource timing monitoring
this.setupResourceTiming();
// Error tracking
this.setupErrorTracking();
// User interaction monitoring
this.setupInteractionTracking();
// Start reporting
this.startPeriodicReporting();
}
setupCoreWebVitals() {
// Largest Contentful Paint (LCP)
this.observeLCP();
// First Input Delay (FID)
this.observeFID();
// Cumulative Layout Shift (CLS)
this.observeCLS();
// First Contentful Paint (FCP)
this.observeFCP();
// Time to First Byte (TTFB)
this.observeTTFB();
}
observeLCP() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
this.recordMetric('lcp', {
value: lastEntry.startTime,
rating: this.rateLCP(lastEntry.startTime),
element: lastEntry.element?.tagName,
url: lastEntry.url,
timestamp: Date.now()
});
// Check for performance issues
if (lastEntry.startTime > this.alertThresholds.lcp) {
this.triggerAlert('lcp_threshold_exceeded', {
value: lastEntry.startTime,
threshold: this.alertThresholds.lcp
});
}
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });
this.observers.set('lcp', observer);
}
}
observeFID() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
this.recordMetric('fid', {
value: entry.processingStart - entry.startTime,
rating: this.rateFID(entry.processingStart - entry.startTime),
eventType: entry.name,
timestamp: Date.now()
});
});
});
observer.observe({ entryTypes: ['first-input'] });
this.observers.set('fid', observer);
}
}
observeCLS() {
if ('PerformanceObserver' in window) {
let clsValue = 0;
let sessionValue = 0;
let sessionEntries = [];
const observer = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
// Only count layout shifts without recent user input
if (!entry.hadRecentInput) {
const firstSessionEntry = sessionEntries[0];
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
// If the entry occurred less than 1 second after the previous entry
// and less than 5 seconds after the first entry in the session,
// include it in the current session
if (sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000) {
sessionValue += entry.value;
sessionEntries.push(entry);
} else {
sessionValue = entry.value;
sessionEntries = [entry];
}
// If the current session value is larger than the current CLS value,
// update CLS and report it
if (sessionValue > clsValue) {
clsValue = sessionValue;
this.recordMetric('cls', {
value: clsValue,
rating: this.rateCLS(clsValue),
entries: sessionEntries.length,
timestamp: Date.now()
});
}
}
});
});
observer.observe({ entryTypes: ['layout-shift'] });
this.observers.set('cls', observer);
}
}
setupCustomMetrics() {
// Time to Interactive (TTI)
this.measureTTI();
// Custom loading metrics
this.measureCustomTimings();
// JavaScript execution time
this.measureJavaScriptPerformance();
// Memory usage monitoring
this.monitorMemoryUsage();
}
measureTTI() {
// Simplified TTI measurement
const observer = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
// Look for long tasks after main content load
entries.forEach(entry => {
if (entry.duration > 50) { // Long task threshold
this.recordMetric('long_task', {
duration: entry.duration,
startTime: entry.startTime,
name: entry.name,
timestamp: Date.now()
});
}
});
});
if ('PerformanceObserver' in window) {
observer.observe({ entryTypes: ['longtask'] });
this.observers.set('longtask', observer);
}
}
measureCustomTimings() {
// Measure critical application milestones
window.addEventListener('load', () => {
// DOM ready time
const domReady = performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart;
this.recordMetric('dom_ready', { value: domReady, timestamp: Date.now() });
// Full page load time
const pageLoad = performance.timing.loadEventEnd - performance.timing.navigationStart;
this.recordMetric('page_load', { value: pageLoad, timestamp: Date.now() });
// JavaScript bundle size and load time
this.measureBundlePerformance();
});
}
measureBundlePerformance() {
const scriptEntries = performance.getEntriesByType('resource')
.filter(entry => entry.initiatorType === 'script');
let totalBundleSize = 0;
let totalLoadTime = 0;
scriptEntries.forEach(entry => {
totalBundleSize += entry.transferSize || 0;
totalLoadTime += entry.duration;
this.recordMetric('script_load', {
url: entry.name,
size: entry.transferSize,
duration: entry.duration,
timestamp: Date.now()
});
});
this.recordMetric('bundle_performance', {
totalSize: totalBundleSize,
totalLoadTime: totalLoadTime,
scriptCount: scriptEntries.length,
timestamp: Date.now()
});
}
monitorMemoryUsage() {
if ('memory' in performance) {
const measureMemory = () => {
const memInfo = performance.memory;
this.recordMetric('memory_usage', {
usedJSSize: memInfo.usedJSSize,
totalJSSize: memInfo.totalJSSize,
jsLimit: memInfo.jsHeapSizeLimit,
utilization: (memInfo.usedJSSize / memInfo.jsHeapSizeLimit) * 100,
timestamp: Date.now()
});
// Alert on high memory usage
const utilization = (memInfo.usedJSSize / memInfo.jsHeapSizeLimit) * 100;
if (utilization > 90) {
this.triggerAlert('high_memory_usage', {
utilization: utilization,
usedMB: Math.round(memInfo.usedJSSize / 1024 / 1024)
});
}
};
// Measure initially and then periodically
measureMemory();
setInterval(measureMemory, 30000); // Every 30 seconds
}
}
setupResourceTiming() {
const observer = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
// Analyze resource loading performance
const resourceMetrics = {
name: entry.name,
type: entry.initiatorType,
size: entry.transferSize,
duration: entry.duration,
domainLookup: entry.domainLookupEnd - entry.domainLookupStart,
connect: entry.connectEnd - entry.connectStart,
request: entry.responseStart - entry.requestStart,
response: entry.responseEnd - entry.responseStart,
timestamp: Date.now()
};
this.recordMetric('resource_timing', resourceMetrics);
// Check for slow resources
if (entry.duration > this.alertThresholds.slowResource) {
this.triggerAlert('slow_resource', {
resource: entry.name,
duration: entry.duration,
type: entry.initiatorType
});
}
});
});
observer.observe({ entryTypes: ['resource'] });
this.observers.set('resource', observer);
}
recordMetric(name, data) {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
const metricData = {
...data,
timestamp: data.timestamp || Date.now(),
sessionId: this.getSessionId(),
userId: this.getUserId(),
pageUrl: window.location.href,
userAgent: navigator.userAgent
};
this.metrics.get(name).push(metricData);
// Keep only recent metrics in memory
const maxEntries = 1000;
const entries = this.metrics.get(name);
if (entries.length > maxEntries) {
entries.splice(0, entries.length - maxEntries);
}
// Trigger real-time processing
this.processMetricInRealTime(name, metricData);
}
processMetricInRealTime(name, data) {
// Calculate running averages and trends
const entries = this.metrics.get(name);
if (entries.length >= 5) {
const recent = entries.slice(-5);
const average = recent.reduce((sum, entry) => sum + (entry.value || 0), 0) / recent.length;
const trend = this.calculateTrend(recent);
// Check for performance degradation
if (trend.direction === 'worsening' && trend.magnitude > 0.2) {
this.triggerAlert('performance_degradation', {
metric: name,
trend: trend,
currentAverage: average
});
}
}
}
startPeriodicReporting() {
// Send metrics to backend every 60 seconds
setInterval(() => {
this.sendMetricsReport();
}, 60000);
// Send report before page unload
window.addEventListener('beforeunload', () => {
this.sendMetricsReport(true);
});
}
async sendMetricsReport(immediate = false) {
if (!this.reportingEndpoint) return;
const report = this.generateMetricsReport();
try {
if (immediate && 'sendBeacon' in navigator) {
// Use sendBeacon for reliable delivery during page unload
navigator.sendBeacon(this.reportingEndpoint, JSON.stringify(report));
} else {
// Regular fetch for periodic reporting
await fetch(this.reportingEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(report)
});
}
// Clear sent metrics
this.clearOldMetrics();
} catch (error) {
console.error('Failed to send performance report:', error);
}
}
generateMetricsReport() {
const report = {
sessionId: this.getSessionId(),
userId: this.getUserId(),
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
connection: this.getConnectionInfo(),
metrics: {}
};
// Aggregate metrics
this.metrics.forEach((entries, name) => {
if (entries.length === 0) return;
const values = entries.map(entry => entry.value).filter(v => v !== undefined);
if (values.length > 0) {
report.metrics[name] = {
count: entries.length,
average: values.reduce((sum, val) => sum + val, 0) / values.length,
min: Math.min(...values),
max: Math.max(...values),
latest: entries[entries.length - 1],
p50: this.calculatePercentile(values, 50),
p90: this.calculatePercentile(values, 90),
p95: this.calculatePercentile(values, 95)
};
}
});
return report;
}
rateLCP(value) {
if (value <= 2500) return 'good';
if (value <= 4000) return 'needs-improvement';
return 'poor';
}
rateFID(value) {
if (value <= 100) return 'good';
if (value <= 300) return 'needs-improvement';
return 'poor';
}
rateCLS(value) {
if (value <= 0.1) return 'good';
if (value <= 0.25) return 'needs-improvement';
return 'poor';
}
}
Practical Example: Complete Performance Dashboard
Let’s create a comprehensive performance monitoring dashboard:
<!-- performance-dashboard.html -->
<div class="performance-dashboard">
<!-- Core Web Vitals Overview -->
<div class="core-vitals-section">
<h2>Core Web Vitals</h2>
<div class="vitals-grid">
<div class="vital-card lcp">
<div class="vital-header">
<h3>Largest Contentful Paint</h3>
<span class="vital-info" title="Measures loading performance">ℹ️</span>
</div>
<div class="vital-value" id="lcp-value">--</div>
<div class="vital-rating" id="lcp-rating">measuring...</div>
<div class="vital-chart">
<canvas id="lcp-chart" width="200" height="60"></canvas>
</div>
</div>
<div class="vital-card fid">
<div class="vital-header">
<h3>First Input Delay</h3>
<span class="vital-info" title="Measures interactivity">ℹ️</span>
</div>
<div class="vital-value" id="fid-value">--</div>
<div class="vital-rating" id="fid-rating">measuring...</div>
<div class="vital-chart">
<canvas id="fid-chart" width="200" height="60"></canvas>
</div>
</div>
<div class="vital-card cls">
<div class="vital-header">
<h3>Cumulative Layout Shift</h3>
<span class="vital-info" title="Measures visual stability">ℹ️</span>
</div>
<div class="vital-value" id="cls-value">--</div>
<div class="vital-rating" id="cls-rating">measuring...</div>
<div class="vital-chart">
<canvas id="cls-chart" width="200" height="60"></canvas>
</div>
</div>
</div>
</div>
<!-- Performance Metrics -->
<div class="performance-metrics-section">
<h3>Detailed Performance Metrics</h3>
<div class="metrics-grid">
<div class="metric-group">
<h4>Loading Performance</h4>
<div class="metric-item">
<span class="metric-label">Time to First Byte</span>
<span class="metric-value" id="ttfb-value">--</span>
</div>
<div class="metric-item">
<span class="metric-label">First Contentful Paint</span>
<span class="metric-value" id="fcp-value">--</span>
</div>
<div class="metric-item">
<span class="metric-label">DOM Content Loaded</span>
<span class="metric-value" id="dcl-value">--</span>
</div>
<div class="metric-item">
<span class="metric-label">Page Load Complete</span>
<span class="metric-value" id="load-value">--</span>
</div>
</div>
<div class="metric-group">
<h4>Resource Performance</h4>
<div class="metric-item">
<span class="metric-label">Total Bundle Size</span>
<span class="metric-value" id="bundle-size">--</span>
</div>
<div class="metric-item">
<span class="metric-label">Script Load Time</span>
<span class="metric-value" id="script-load-time">--</span>
</div>
<div class="metric-item">
<span class="metric-label">Image Load Time</span>
<span class="metric-value" id="image-load-time">--</span>
</div>
<div class="metric-item">
<span class="metric-label">CSS Load Time</span>
<span class="metric-value" id="css-load-time">--</span>
</div>
</div>
<div class="metric-group">
<h4>Memory & CPU</h4>
<div class="metric-item">
<span class="metric-label">Memory Usage</span>
<span class="metric-value" id="memory-usage">--</span>
</div>
<div class="metric-item">
<span class="metric-label">JavaScript Heap Size</span>
<span class="metric-value" id="heap-size">--</span>
</div>
<div class="metric-item">
<span class="metric-label">Long Tasks Count</span>
<span class="metric-value" id="long-tasks">--</span>
</div>
<div class="metric-item">
<span class="metric-label">CPU Utilization</span>
<span class="metric-value" id="cpu-usage">--</span>
</div>
</div>
</div>
</div>
<!-- Real-time Performance Chart -->
<div class="performance-chart-section">
<h3>Real-time Performance Trends</h3>
<div class="chart-controls">
<select id="chart-metric">
<option value="lcp">Largest Contentful Paint</option>
<option value="fid">First Input Delay</option>
<option value="cls">Cumulative Layout Shift</option>
<option value="memory">Memory Usage</option>
</select>
<select id="chart-timeframe">
<option value="5m">Last 5 minutes</option>
<option value="15m">Last 15 minutes</option>
<option value="1h">Last hour</option>
<option value="24h">Last 24 hours</option>
</select>
</div>
<div class="chart-container">
<canvas id="performance-trend-chart" width="800" height="300"></canvas>
</div>
</div>
<!-- Performance Insights -->
<div class="performance-insights-section">
<h3>Performance Insights & Recommendations</h3>
<div id="insights-container">
<!-- Insights will be populated dynamically -->
</div>
</div>
</div>
<script>
// Performance Dashboard Implementation
class PerformanceDashboard {
constructor() {
this.charts = new Map();
this.metrics = new Map();
this.performanceMonitor = new AdvancedPerformanceMonitor({
reportingEndpoint: '/api/performance/metrics',
alertThresholds: {
lcp: 2500,
fid: 100,
cls: 0.1,
slowResource: 1000
}
});
this.initializeDashboard();
this.startRealTimeUpdates();
}
initializeDashboard() {
// Initialize charts
this.initializeCharts();
// Setup metric listeners
this.setupMetricListeners();
// Load initial data
this.loadInitialMetrics();
// Setup controls
this.setupControls();
}
initializeCharts() {
// Initialize Core Web Vitals charts
this.charts.set('lcp', this.createVitalChart('lcp-chart', '#4285f4'));
this.charts.set('fid', this.createVitalChart('fid-chart', '#34a853'));
this.charts.set('cls', this.createVitalChart('cls-chart', '#fbbc04'));
// Initialize main performance trend chart
this.charts.set('trend', this.createTrendChart('performance-trend-chart'));
}
createVitalChart(canvasId, color) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
return new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
data: [],
borderColor: color,
backgroundColor: color + '20',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { display: false },
y: { display: false }
},
plugins: {
legend: { display: false }
},
elements: {
point: { radius: 0 }
}
}
});
}
setupMetricListeners() {
// Listen for Core Web Vitals updates
this.performanceMonitor.on('lcp', (data) => this.updateVital('lcp', data));
this.performanceMonitor.on('fid', (data) => this.updateVital('fid', data));
this.performanceMonitor.on('cls', (data) => this.updateVital('cls', data));
// Listen for other performance metrics
this.performanceMonitor.on('memory_usage', (data) => this.updateMemoryMetrics(data));
this.performanceMonitor.on('bundle_performance', (data) => this.updateBundleMetrics(data));
this.performanceMonitor.on('resource_timing', (data) => this.updateResourceMetrics(data));
}
updateVital(vitalName, data) {
// Update vital card display
document.getElementById(`${vitalName}-value`).textContent = this.formatVitalValue(vitalName, data.value);
document.getElementById(`${vitalName}-rating`).textContent = data.rating;
document.getElementById(`${vitalName}-rating`).className = `vital-rating ${data.rating}`;
// Update vital chart
const chart = this.charts.get(vitalName);
chart.data.labels.push(new Date().toLocaleTimeString());
chart.data.datasets[0].data.push(data.value);
// Keep only last 20 data points
if (chart.data.labels.length > 20) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update('none');
// Generate insights
this.generateInsights(vitalName, data);
}
formatVitalValue(vitalName, value) {
switch (vitalName) {
case 'lcp':
case 'fid':
return `${Math.round(value)}ms`;
case 'cls':
return value.toFixed(3);
default:
return value.toString();
}
}
generateInsights(metricName, data) {
const insights = [];
// Generate specific insights based on metric performance
if (metricName === 'lcp' && data.rating === 'poor') {
insights.push({
type: 'warning',
title: 'Slow Largest Contentful Paint',
description: 'Your LCP is above 4 seconds. Consider optimizing images and reducing server response times.',
recommendations: [
'Optimize critical images with modern formats (WebP, AVIF)',
'Implement proper image lazy loading',
'Reduce server response times',
'Remove unused CSS and JavaScript'
]
});
}
if (metricName === 'fid' && data.rating === 'poor') {
insights.push({
type: 'error',
title: 'High First Input Delay',
description: 'Users are experiencing delays when interacting with your site.',
recommendations: [
'Break up long-running JavaScript tasks',
'Use web workers for heavy computations',
'Optimize third-party scripts',
'Implement code splitting'
]
});
}
if (metricName === 'cls' && data.rating === 'poor') {
insights.push({
type: 'warning',
title: 'Layout Instability Detected',
description: 'Elements are shifting during page load, affecting user experience.',
recommendations: [
'Set explicit dimensions for images and videos',
'Reserve space for dynamic content',
'Avoid inserting content above existing content',
'Use CSS transforms instead of changing layout properties'
]
});
}
// Update insights display
this.updateInsightsDisplay(insights);
}
updateInsightsDisplay(insights) {
const container = document.getElementById('insights-container');
insights.forEach(insight => {
const insightElement = document.createElement('div');
insightElement.className = `insight-card ${insight.type}`;
insightElement.innerHTML = `
<div class="insight-header">
<h4>${insight.title}</h4>
<span class="insight-type">${insight.type}</span>
</div>
<p class="insight-description">${insight.description}</p>
<div class="insight-recommendations">
<h5>Recommendations:</h5>
<ul>
${insight.recommendations.map(rec => `<li>${rec}</li>`).join('')}
</ul>
</div>
<button class="btn btn-sm btn-outline-primary dismiss-insight"
onclick="this.parentElement.remove()">
Dismiss
</button>
`;
container.appendChild(insightElement);
});
}
startRealTimeUpdates() {
// Update dashboard every 5 seconds
setInterval(() => {
this.updateDashboard();
}, 5000);
}
updateDashboard() {
// Update current performance metrics
this.updateCurrentMetrics();
// Update trend chart if visible
this.updateTrendChart();
// Check for performance alerts
this.checkPerformanceAlerts();
}
updateCurrentMetrics() {
// Update loading performance metrics
const timing = performance.timing;
const now = Date.now();
if (timing.responseStart > 0) {
const ttfb = timing.responseStart - timing.navigationStart;
document.getElementById('ttfb-value').textContent = `${ttfb}ms`;
}
if (timing.domContentLoadedEventEnd > 0) {
const dcl = timing.domContentLoadedEventEnd - timing.navigationStart;
document.getElementById('dcl-value').textContent = `${dcl}ms`;
}
if (timing.loadEventEnd > 0) {
const load = timing.loadEventEnd - timing.navigationStart;
document.getElementById('load-value').textContent = `${load}ms`;
}
// Update memory metrics if available
if ('memory' in performance) {
const memory = performance.memory;
document.getElementById('memory-usage').textContent =
`${Math.round(memory.usedJSSize / 1024 / 1024)}MB`;
document.getElementById('heap-size').textContent =
`${Math.round(memory.totalJSSize / 1024 / 1024)}MB`;
}
}
}
// Initialize performance dashboard when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
window.performanceDashboard = new PerformanceDashboard();
});
</script>
Troubleshooting Common Issues
Performance Optimization Debugging:
- Slow Initial Load: Implement critical resource prioritization and eliminate render-blocking resources
- High Memory Usage: Optimize JavaScript execution and implement proper garbage collection
- Layout Shifts: Set explicit dimensions and reserve space for dynamic content
- Cache Misses: Optimize cache strategies and implement intelligent pre-loading
- Mobile Performance: Implement device-specific optimizations and adaptive loading
Performance Best Practices
Building High-Performance Portal Architecture:
- Critical Resource Prioritization: Load essential content first using resource hints and preloading
- Progressive Enhancement: Build baseline functionality that works everywhere, then enhance
- Adaptive Loading: Adjust content delivery based on device capabilities and network conditions
- Monitoring-Driven Optimization: Use real-time data to guide optimization decisions
- Performance Budgets: Set and enforce performance constraints throughout development
Key Takeaways
Performance optimization is an ongoing process that requires comprehensive monitoring, intelligent caching, and adaptive delivery strategies. Focus on Core Web Vitals, implement multi-level caching, optimize assets for different device profiles, and maintain real-time performance visibility to ensure your portal delivers exceptional experiences that drive user engagement and business success.
This concludes the Advanced Customization section of our learning series. In the next section, you’ll dive into Pro-Code Development with advanced JavaScript frameworks, custom components, and enterprise integration patterns.
- Verify functionality across different browsers and devices
- Validate accessibility compliance
- Performance test under load conditions
4. Documentation and Maintenance
Set yourself up for long-term success:
- Document all customizations and their purpose
- Create maintenance procedures and schedules
- Plan for future updates and changes
- Train team members on your customizations
Practical Example
Consider implementing this episode’s techniques for a customer portal where users need enhanced functionality beyond basic templates. The implementation should:
- Provide an intuitive, branded user experience
- Handle complex business logic appropriately
- Perform well under realistic usage conditions
- Be maintainable by your development team
Apply the specific techniques from this episode while following the general principles of good portal development.
Common Implementation Patterns
| Pattern | When to Use | Benefits | Considerations |
|---|---|---|---|
| Progressive Enhancement | Adding advanced features | Ensures basic functionality always works | Requires careful planning |
| Responsive Design | Multi-device support | Consistent experience across devices | May increase complexity |
| Modular Development | Complex customizations | Easier maintenance and updates | Requires good architecture |
| Performance Monitoring | All implementations | Early detection of issues | Ongoing effort required |
Troubleshooting Tips
- Performance Issues: Monitor page load times and optimize images, scripts, and database queries
- Browser Compatibility: Test across different browsers and use progressive enhancement
- Mobile Experience: Ensure all functionality works well on mobile devices
- Maintenance Challenges: Keep good documentation and follow consistent coding standards
Advanced Considerations
As you implement these intermediate techniques, consider:
- Scalability: How will your solution perform as usage grows?
- Security: Do your customizations introduce any security vulnerabilities?
- Integration: How do your changes affect other portal functionality?
- Future Updates: Will your customizations survive platform updates?
Summary
Performance optimization is an ongoing process, not a one-time task. Implement monitoring from the start, optimize proactively rather than reactively, and always consider performance impact when adding new features. Congratulations on completing the intermediate section! In the advanced section, you’ll learn pro-code development techniques.