Back to Blog
PerformanceWeb DevelopmentCore Web VitalsOptimization

Web Performance Fundamentals Every Developer Should Know

Master the fundamentals of web performance. From Core Web Vitals to optimization techniques that actually matter.

B
Bootspring Team
Engineering
July 5, 2025
5 min read

Performance isn't optional—it's a feature. Slow sites lose users, hurt SEO, and frustrate developers. Understanding performance fundamentals helps you build fast sites from the start rather than optimizing later.

Core Web Vitals#

LCP (Largest Contentful Paint)#

Time until the largest content element is visible.

Target: < 2.5 seconds

Common culprits:

  • Large unoptimized images
  • Slow server response
  • Render-blocking resources
  • Client-side rendering

Fixes:

1<!-- Preload critical images --> 2<link rel="preload" as="image" href="hero.jpg"> 3 4<!-- Use responsive images --> 5<img 6 srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w" 7 sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px" 8 src="hero-800.jpg" 9 alt="Hero image" 10> 11 12<!-- Modern formats --> 13<picture> 14 <source srcset="hero.avif" type="image/avif"> 15 <source srcset="hero.webp" type="image/webp"> 16 <img src="hero.jpg" alt="Hero"> 17</picture>

FID/INP (Interaction to Next Paint)#

Time from user interaction to browser response.

Target: < 200ms

Common culprits:

  • Long JavaScript tasks
  • Heavy main thread work
  • Synchronous operations

Fixes:

1// Break up long tasks 2function processLargeArray(items) { 3 const chunkSize = 100; 4 let index = 0; 5 6 function processChunk() { 7 const chunk = items.slice(index, index + chunkSize); 8 chunk.forEach(processItem); 9 index += chunkSize; 10 11 if (index < items.length) { 12 // Yield to browser 13 requestIdleCallback(processChunk); 14 } 15 } 16 17 processChunk(); 18} 19 20// Use Web Workers for heavy computation 21const worker = new Worker('heavy-computation.js'); 22worker.postMessage(data); 23worker.onmessage = (e) => updateUI(e.data);

CLS (Cumulative Layout Shift)#

Visual stability—how much the page shifts unexpectedly.

Target: < 0.1

Common culprits:

  • Images without dimensions
  • Dynamically injected content
  • Web fonts causing FOUT/FOIT

Fixes:

1<!-- Always specify dimensions --> 2<img src="photo.jpg" width="800" height="600" alt="Photo"> 3 4<!-- Or use aspect-ratio --> 5<style> 6 .image-container { 7 aspect-ratio: 16 / 9; 8 width: 100%; 9 } 10</style> 11 12<!-- Reserve space for dynamic content --> 13<div class="ad-slot" style="min-height: 250px;"> 14 <!-- Ad loads here --> 15</div> 16 17<!-- Prevent font swap shift --> 18<style> 19 @font-face { 20 font-family: 'MyFont'; 21 font-display: optional; /* or swap with size-adjust */ 22 size-adjust: 105%; 23 } 24</style>

Resource Loading#

Critical Rendering Path#

1<!-- Critical CSS inline --> 2<style> 3 /* Above-the-fold styles only */ 4 .header { ... } 5 .hero { ... } 6</style> 7 8<!-- Defer non-critical CSS --> 9<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'"> 10 11<!-- Defer JavaScript --> 12<script src="app.js" defer></script> 13 14<!-- Async for independent scripts --> 15<script src="analytics.js" async></script>

Resource Hints#

1<!-- DNS prefetch for third parties --> 2<link rel="dns-prefetch" href="//api.example.com"> 3 4<!-- Preconnect for critical origins --> 5<link rel="preconnect" href="https://fonts.googleapis.com"> 6 7<!-- Preload critical resources --> 8<link rel="preload" href="critical.js" as="script"> 9<link rel="preload" href="hero.jpg" as="image"> 10 11<!-- Prefetch for likely next page --> 12<link rel="prefetch" href="/next-page.html"> 13 14<!-- Prerender (aggressive) --> 15<link rel="prerender" href="/likely-destination">

JavaScript Optimization#

Bundle Optimization#

1// Dynamic imports for code splitting 2const HeavyComponent = lazy(() => import('./HeavyComponent')); 3 4// Route-based splitting 5const routes = [ 6 { 7 path: '/dashboard', 8 component: lazy(() => import('./pages/Dashboard')), 9 }, 10]; 11 12// Conditional loading 13if (user.isPremium) { 14 const PremiumFeatures = await import('./PremiumFeatures'); 15}

Tree Shaking#

1// ❌ Imports entire library 2import _ from 'lodash'; 3_.debounce(fn, 300); 4 5// ✅ Imports only what's needed 6import debounce from 'lodash/debounce'; 7debounce(fn, 300); 8 9// ✅ Or use ES modules 10import { debounce } from 'lodash-es';

Avoiding Main Thread Blocking#

1// Use requestIdleCallback for non-urgent work 2requestIdleCallback(() => { 3 // Analytics, preloading, etc. 4 trackEvent('page_view'); 5}); 6 7// Use IntersectionObserver for lazy loading 8const observer = new IntersectionObserver((entries) => { 9 entries.forEach(entry => { 10 if (entry.isIntersecting) { 11 loadImage(entry.target); 12 observer.unobserve(entry.target); 13 } 14 }); 15}); 16 17document.querySelectorAll('img[data-src]').forEach(img => { 18 observer.observe(img); 19});

Image Optimization#

Modern Formats#

# Convert to WebP cwebp -q 80 input.jpg -o output.webp # Convert to AVIF avifenc input.jpg output.avif

Responsive Images#

1<img 2 src="image-800.jpg" 3 srcset=" 4 image-400.jpg 400w, 5 image-800.jpg 800w, 6 image-1200.jpg 1200w 7 " 8 sizes=" 9 (max-width: 400px) 400px, 10 (max-width: 800px) 800px, 11 1200px 12 " 13 loading="lazy" 14 decoding="async" 15 alt="Description" 16>

Image CDN Usage#

<!-- Cloudinary example --> <img src="https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg"> <!-- Imgix example --> <img src="https://example.imgix.net/image.jpg?w=800&auto=format,compress">

Caching Strategy#

HTTP Caching#

1# Static assets - long cache 2location /static/ { 3 expires 1y; 4 add_header Cache-Control "public, immutable"; 5} 6 7# HTML - short cache 8location / { 9 add_header Cache-Control "no-cache"; 10} 11 12# API - no cache 13location /api/ { 14 add_header Cache-Control "no-store"; 15}

Service Worker Caching#

1// Cache static assets 2self.addEventListener('install', (event) => { 3 event.waitUntil( 4 caches.open('static-v1').then((cache) => { 5 return cache.addAll([ 6 '/styles.css', 7 '/app.js', 8 '/offline.html', 9 ]); 10 }) 11 ); 12}); 13 14// Network-first for API, cache-first for assets 15self.addEventListener('fetch', (event) => { 16 if (event.request.url.includes('/api/')) { 17 event.respondWith(networkFirst(event.request)); 18 } else { 19 event.respondWith(cacheFirst(event.request)); 20 } 21});

Measuring Performance#

Lab Tools#

# Lighthouse CLI lighthouse https://example.com --output html # WebPageTest webpagetest test https://example.com

Field Data#

1// Report Core Web Vitals 2import { onCLS, onFID, onLCP } from 'web-vitals'; 3 4function sendToAnalytics(metric) { 5 analytics.track('web_vital', { 6 name: metric.name, 7 value: metric.value, 8 id: metric.id, 9 }); 10} 11 12onCLS(sendToAnalytics); 13onFID(sendToAnalytics); 14onLCP(sendToAnalytics);

Performance Budget#

1{ 2 "budgets": [ 3 { 4 "resourceType": "script", 5 "budget": 300 6 }, 7 { 8 "resourceType": "total", 9 "budget": 1000 10 }, 11 { 12 "metric": "first-contentful-paint", 13 "budget": 1500 14 } 15 ] 16}

Common Pitfalls#

Third-Party Scripts#

1<!-- Load third parties after page load --> 2<script> 3 window.addEventListener('load', () => { 4 const script = document.createElement('script'); 5 script.src = 'https://third-party.com/widget.js'; 6 document.body.appendChild(script); 7 }); 8</script> 9 10<!-- Or use facade pattern --> 11<div class="youtube-facade" data-video-id="abc123"> 12 <img src="thumbnail.jpg" alt="Video"> 13 <button>Play</button> 14</div> 15<script> 16 document.querySelector('.youtube-facade').addEventListener('click', () => { 17 // Load YouTube iframe only when clicked 18 }); 19</script>

Font Loading#

1/* Optimize font loading */ 2@font-face { 3 font-family: 'MyFont'; 4 src: url('font.woff2') format('woff2'); 5 font-display: swap; 6 unicode-range: U+0000-00FF; /* Basic Latin only */ 7}

Conclusion#

Web performance is a continuous practice, not a one-time fix. Understand the metrics that matter, measure consistently, and optimize deliberately. The fundamentals—efficient loading, minimal JavaScript, optimized images, smart caching—provide the foundation for fast experiences.

Start with Core Web Vitals, establish performance budgets, and make performance part of your development workflow. Fast sites win.

Share this article

Help spread the word about Bootspring