Performance Overview
Web performance determines whether users stay or leave. A one-second delay in page load can reduce conversions by 7%, and 53% of mobile users abandon sites that take longer than three seconds to load. Performance isn't just a technical metric—it's a direct line to revenue, retention, and search rankings.
This section covers the metrics that matter, the strategies that work, and the tools you need to diagnose and fix performance problems in modern web applications.
Why Performance Matters
Business Impact
The data is consistent across industries:
- Amazon found that every 100ms of latency cost them 1% in sales
- Google discovered that a 0.5-second delay in search results caused a 20% drop in traffic
- Pinterest increased search engine traffic by 15% after reducing perceived wait time by 40%
Performance directly affects:
| Metric | Impact |
|---|---|
| Conversion Rate | 2x higher for sites loading in 2s vs 5s |
| Bounce Rate | +32% when load time goes from 1s to 3s |
| SEO Rankings | Core Web Vitals are a ranking factor since 2021 |
| Ad Revenue | Faster pages = more pageviews = more impressions |
Mobile Reality
Over 60% of web traffic is mobile, often on 3G/4G connections with high latency. Your blazing-fast development machine doesn't represent your users:
Your MacBook Pro: ~50ms latency, 1Gbps
Average mobile user: ~100-300ms latency, 10-50Mbps (effective)
Emerging markets: ~300-600ms latency, 2-10Mbps
Always test on throttled connections. Chrome DevTools' "Slow 3G" preset (400ms RTT, 400kbps) is a good stress test.
Perceived vs. Actual Performance
A page that loads in 3 seconds can feel faster than one loading in 2 seconds. Perception matters more than stopwatch measurements.
The Psychology of Waiting
Users perceive wait time differently based on:
- Active vs. passive waiting: Progress indicators make waits feel shorter
- Known vs. unknown duration: "Loading 3 of 5 images" beats a spinner
- Occupied vs. unoccupied time: Show content progressively, not all at once
Skeleton Screens vs. Spinners
Spinners tell users "wait." Skeleton screens tell users "content is coming."
<!-- Spinner: User stares at nothing -->
<div class="loading-spinner"></div>
<!-- Skeleton: User sees the shape of content -->
<div class="skeleton">
<div class="skeleton-avatar"></div>
<div class="skeleton-text"></div>
<div class="skeleton-text short"></div>
</div>
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Facebook, LinkedIn, and YouTube all use skeleton screens because they reduce perceived load time by 10-20%, even when actual load time is unchanged.
Optimistic UI Updates
Don't wait for the server to confirm actions the user just performed:
// ❌ Slow: Wait for server before updating UI
async function likePost(postId: string) {
const response = await fetch(`/api/posts/${postId}/like`, { method: 'POST' });
if (response.ok) {
setLiked(true); // UI updates after network round-trip
}
}
// ✅ Fast: Update UI immediately, rollback on error
async function likePost(postId: string) {
setLiked(true); // Instant feedback
try {
await fetch(`/api/posts/${postId}/like`, { method: 'POST' });
} catch {
setLiked(false); // Rollback on failure
showToast('Failed to like post');
}
}
The 3-Second Rule
Three seconds is the threshold where most users give up. Your performance budget should work backward from this goal.
Setting a Performance Budget
A budget allocates your 3-second window across different resource types:
| Resource Type | Budget | Rationale |
|---|---|---|
| HTML | 50KB | First byte should be fast |
| CSS (critical) | 30KB | Render-blocking, must be small |
| JavaScript (initial) | 150KB | Main thread blocking |
| Images (above fold) | 200KB | LCP optimization |
| Fonts | 100KB | 2-3 font files max |
| Total | ~530KB | Leaves room for network variance |
Enforcement
Automate budget enforcement in your build process:
// vite.config.ts - Build warnings for large chunks
export default defineConfig({
build: {
chunkSizeWarningLimit: 150, // KB
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
// Keep vendor bundle separate and cacheable
}
}
}
}
});
// bundlesize.config.json - CI enforcement
{
"files": [
{ "path": "dist/assets/*.js", "maxSize": "150 kB" },
{ "path": "dist/assets/*.css", "maxSize": "30 kB" },
{ "path": "dist/index.html", "maxSize": "50 kB" }
]
}
Tools like bundlesize or size-limit can fail CI builds when budgets are exceeded.
Performance Optimization Strategies
Performance work falls into three categories:
1. Reduce Bytes
Less data = faster transfer:
- Minify JavaScript and CSS (handled by Vite, webpack, etc.)
- Compress with Brotli/gzip at the server level
- Use modern image formats (WebP, AVIF)
- Remove unused code (tree shaking, dead code elimination)
- Subset fonts to only required characters
See the Asset Optimization article for detailed techniques.
2. Reduce Requests
Fewer round-trips = lower latency impact:
- Bundle CSS and JavaScript appropriately
- Inline critical CSS in
<head> - Use HTTP/2 or HTTP/3 (multiplexing reduces connection overhead)
- Combine icon sprites or use inline SVG
- Leverage browser caching aggressively
See the Caching Strategies article for header configuration.
3. Reduce Blocking
Unblock the render path:
- Defer non-critical JavaScript with
asyncordefer - Lazy load below-the-fold images
- Use
font-display: swapto prevent invisible text - Avoid layout shifts (reserve space for dynamic content)
- Split code by route—don't ship code users won't need
See the Loading Strategies article for implementation patterns.
The Critical Rendering Path
Understanding how browsers render pages helps you optimize effectively:
1. HTML Download
↓
2. Parse HTML → Build DOM
↓ (blocked by CSS)
3. Parse CSS → Build CSSOM
↓
4. Combine DOM + CSSOM → Render Tree
↓ (blocked by JS if not deferred)
5. Layout (calculate positions)
↓
6. Paint (pixels to screen)
Key insight: CSS and synchronous JavaScript block rendering. This is why:
- Critical CSS should be inlined
- Non-critical CSS should use
mediaqueries or be loaded asynchronously - JavaScript should use
defer(preferred) orasync
<!-- Optimal resource loading order -->
<head>
<!-- Critical CSS inline -->
<style>/* above-the-fold styles */</style>
<!-- Preload important resources -->
<link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin>
<link rel="preload" href="/hero.webp" as="image">
<!-- Non-critical CSS loaded async -->
<link rel="preload" href="/styles/main.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
<!-- JavaScript deferred -->
<script src="/app.js" defer></script>
</head>
Quick Wins Checklist
Before diving into complex optimizations, ensure you've covered the basics:
- Enable gzip/Brotli compression on your server
- Set appropriate
Cache-Controlheaders - Use a CDN for static assets
- Add
loading="lazy"to below-fold images - Add
deferto non-critical scripts - Set explicit
widthandheighton images - Serve WebP images with JPEG/PNG fallbacks
- Subset and self-host fonts
- Enable HTTP/2 on your server
- Remove unused CSS and JavaScript
These fundamentals often provide 50-70% of potential performance gains.
Measuring Performance
You can't improve what you don't measure. Key tools:
- Lab data: Lighthouse, WebPageTest, Chrome DevTools
- Field data: Chrome User Experience Report (CrUX), Real User Monitoring (RUM)
Lab data is consistent and reproducible. Field data reflects real user experience. Both are valuable.
See the Profiling & Debugging article for detailed tool usage.
Next Steps
This section covers:
- Core Web Vitals: The metrics Google uses for ranking—LCP, CLS, and INP
- Loading Strategies: Code splitting, lazy loading, and resource hints
- Asset Optimization: Images, fonts, and other static resources
- Caching Strategies: Browser, CDN, and server-side caching
- Profiling & Debugging: Finding and fixing performance bottlenecks
See Also
- Vite - Modern build tool with excellent code splitting defaults
- Docker Deployment - Configuring compression and caching at the container level
- Astro - Framework designed for performance with partial hydration