Web Application Performance Optimization Guide
Overview
Web application performance optimization is essential for improving user experience and SEO rankings. This guide comprehensively covers the latest performance optimization techniques for 2025, including Core Web Vitals improvements, bundle optimization, and caching strategies.
Details
Core Web Vitals Optimization
Core Web Vitals are three critical metrics defined by Google that measure the user experience of web pages.
LCP (Largest Contentful Paint)
- Good: Within 2.5 seconds
- Needs Improvement: Between 2.5 and 4.0 seconds
- Poor: Over 4.0 seconds
Optimization techniques:
- Use
fetchpriority="high"
for critical resources - Adopt WebP/AVIF formats and implement responsive images
- Leverage CDN and improve server performance
- Eliminate render-blocking resources
INP (Interaction to Next Paint)
- Good: Under 200 milliseconds
- Needs Improvement: Between 200 and 500 milliseconds
- Poor: Over 500 milliseconds
Optimization techniques:
- Optimize JavaScript execution and break down long tasks
- Reduce third-party scripts
- Lighten event handlers
- Prioritize critical UI elements
CLS (Cumulative Layout Shift)
- Good: Less than 0.1
- Needs Improvement: Between 0.1 and 0.25
- Poor: Above 0.25
Optimization techniques:
- Always specify dimensions for images, videos, and ads
- Avoid injecting dynamic content above existing content
- Implement lazy loading properly
JavaScript Bundle Optimization
Code Splitting
A technique that breaks down applications into smaller chunks, loading only necessary parts on demand.
Implementation with Webpack:
- Utilize dynamic imports (
import()
) - Extract common dependencies with SplitChunksPlugin
- Fine control with Magic Comments
Implementation with Vite:
- Native ES module support
- Automatic
<link rel="modulepreload">
directive generation - CSS splitting control with
build.cssCodeSplit
Lazy Loading
Defer non-essential resources until needed to reduce initial load time.
Best practices:
- Start with route-based code splitting
- Avoid over-splitting (balance with HTTP request count)
- Preload critical resources with
preload
attribute - Apply to conditionally rendered components
Image Optimization
Modern Image Formats
AVIF:
- Up to 50% smaller than JPEG, 20-30% smaller than WebP
- Superior compression efficiency and quality
- Wide Color Gamut (WCG) and High Dynamic Range (HDR) support
WebP:
- 25-34% file size reduction compared to JPEG
- Wider browser support
- Better encoding/decoding speed than AVIF
Implementation Pattern
<picture>
<source srcset="/path/to/img.avif" type="image/avif">
<source srcset="/path/to/img.webp" type="image/webp">
<img src="/path/to/img.jpeg" alt="" loading="lazy">
</picture>
Caching Strategies
Service Worker Caching
- Cache First: Responsive but not updated
- Stale While Revalidate: Responsive with moderate freshness
- Network First: Optimal for real-time data
- Network Only: When always fresh data is required
Layered Caching Architecture
- Service Worker cache
- HTTP cache (browser cache)
- CDN cache
- Origin server
Cache Header Configuration
Cache-Control: max-age=3600, stale-while-revalidate=86400
Performance Monitoring
Real User Monitoring (RUM)
Collect performance data from actual users and continuously monitor Core Web Vitals.
Major RUM tools:
- DebugBear: Core Web Vitals focused
- RUMvision: 24/7 site speed monitoring
- Datadog RUM: Comprehensive visibility
- SpeedCurve: User experience optimization
Third-party Script Optimization
- Evaluate external script impact
- Implement asynchronous loading
- Remove unnecessary scripts
- Set performance budgets
Pros & Cons
Pros
- Improved User Experience: Reduced bounce rates through faster page loads
- Better SEO Rankings: Core Web Vitals are Google ranking factors
- Higher Conversion Rates: Yahoo! JAPAN achieved 15.1% increase in page views
- Reduced Bandwidth: Up to 70% data transfer reduction through optimization
- Enhanced Mobile Experience: Significant improvements especially on slow networks
Cons
- Implementation Complexity: Requires understanding and implementing numerous optimization techniques
- Increased Development Time: Initial setup and testing take time
- Browser Compatibility: Latest technologies may not work on older browsers
- Ongoing Maintenance: Requires continuous monitoring and adjustment of performance metrics
- Complex Resource Management: Managing caching strategies and versioning
References
- Core Web Vitals - Google Developers
- web.dev - Performance
- Webpack Code Splitting Guide
- Vite Features
- MDN - Progressive Web Apps Caching
- Chrome DevTools
Examples
Core Web Vitals Measurement
// Measuring Core Web Vitals using web-vitals library
import {getCLS, getFID, getLCP, getINP} from 'web-vitals';
function sendToAnalytics(metric) {
// Example sending to Google Analytics 4
gtag('event', metric.name, {
value: Math.round(metric.value),
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta,
});
}
// Measure each metric
getCLS(sendToAnalytics);
getLCP(sendToAnalytics);
getINP(sendToAnalytics);
Webpack Code Splitting
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
}
};
// Using dynamic imports
const loadComponent = async () => {
const module = await import(
/* webpackChunkName: "my-component" */
/* webpackPrefetch: true */
'./MyComponent'
);
return module.default;
};
Vite Optimization Configuration
// vite.config.js
import { defineConfig } from 'vite';
import compression from 'vite-plugin-compression';
export default defineConfig({
build: {
// Code splitting configuration
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'axios']
}
}
},
// CSS splitting control
cssCodeSplit: true,
// Build target
target: 'es2015'
},
plugins: [
// gzip compression
compression({
algorithm: 'gzip',
ext: '.gz'
}),
// Brotli compression
compression({
algorithm: 'brotliCompress',
ext: '.br'
})
]
});
Service Worker Caching Strategy
// service-worker.js
const CACHE_NAME = 'app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js'
];
// Cache on install
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
// Stale While Revalidate strategy
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Return cache if available
if (response) {
// Update in background
fetch(event.request)
.then(fetchResponse => {
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, fetchResponse.clone());
});
});
return response;
}
// Fetch from network if not cached
return fetch(event.request);
})
);
});
Image Optimization Implementation
// React component with optimized images
import React from 'react';
const OptimizedImage = ({ src, alt, sizes }) => {
const filename = src.split('.').slice(0, -1).join('.');
return (
<picture>
<source
type="image/avif"
srcSet={`
${filename}.avif 1x,
${filename}@2x.avif 2x,
${filename}@3x.avif 3x
`}
sizes={sizes}
/>
<source
type="image/webp"
srcSet={`
${filename}.webp 1x,
${filename}@2x.webp 2x,
${filename}@3x.webp 3x
`}
sizes={sizes}
/>
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
sizes={sizes}
/>
</picture>
);
};
// Usage example
<OptimizedImage
src="/images/hero.jpg"
alt="Hero image"
sizes="(max-width: 768px) 100vw, 50vw"
/>
Performance Monitoring
// RUM implementation example
class PerformanceMonitor {
constructor(endpoint) {
this.endpoint = endpoint;
this.metrics = {};
this.initializeObservers();
}
initializeObservers() {
// Monitor LCP with PerformanceObserver
const lcpObserver = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.renderTime || lastEntry.loadTime;
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
// Monitor layout shifts
const clsObserver = new PerformanceObserver((entryList) => {
let cls = 0;
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
}
}
this.metrics.cls = cls;
});
clsObserver.observe({ entryTypes: ['layout-shift'] });
// Monitor interactions
const inpObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.interactionId) {
this.metrics.inp = Math.max(
this.metrics.inp || 0,
entry.duration
);
}
}
});
inpObserver.observe({ entryTypes: ['event'] });
}
sendMetrics() {
fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: window.location.href,
metrics: this.metrics,
timestamp: Date.now()
})
});
}
}
// Usage example
const monitor = new PerformanceMonitor('/api/metrics');
// Send metrics on page unload
window.addEventListener('pagehide', () => monitor.sendMetrics());