Performance

Web Performance Optimization Guide

P

Prasanna Joshi

Author

September 20, 2024
22 min read

Understanding Core Web Vitals

Core Web Vitals are three key metrics introduced by Google that measure real-world user experience on your website. These metrics directly impact your SEO ranking and user satisfaction. Understanding and optimizing these metrics is essential for modern web development.

The Three Core Web Vitals

  • Largest Contentful Paint (LCP): Measures loading performance. It marks the point when the largest content element becomes visible. Aim for LCP under 2.5 seconds. This ensures your users can see the main content quickly.
  • First Input Delay (FID) / Interaction to Next Paint (INP): Measures interactivity. It quantifies the delay between user input and browser response. Target under 100ms. Fast interactivity makes your site feel responsive.
  • Cumulative Layout Shift (CLS): Measures visual stability. It tracks unexpected layout shifts during loading. Keep CLS below 0.1. A stable layout provides a better user experience.

Image Optimization - The Biggest Impact

Images typically account for 50-80% of page load time. Proper optimization is crucial for performance.

Format Selection

Choose the right format for the right use case:

  • WebP: Modern format, 25-35% smaller than JPEG/PNG. Use with fallbacks for older browsers.
  • AVIF: Newest format, even better compression than WebP. Growing browser support.
  • JPEG: Best for photographs. Use quality 75-85 for good balance.
  • PNG: Use for images requiring transparency or lossless compression.
  • SVG: Perfect for logos and icons. Scales infinitely, can be animated.
<!-- Serve multiple formats with fallback -->
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg">
  <img src="image.jpg" alt="Description">
</picture>

Responsive Images

Serve different image sizes for different screen sizes:

<!-- srcset with pixel density -->
<img 
  src="image.jpg"
  srcset="
    image-small.jpg 1x,
    image-small-2x.jpg 2x"
  alt="Description"
>

<!-- srcset with viewport widths -->
<img
  src="image.jpg"
  srcset="
    image-300.jpg 300w,
    image-600.jpg 600w,
    image-1200.jpg 1200w"
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
  alt="Description"
>

Image Compression

# Using ImageMagick
convert input.jpg -quality 80 output.jpg

# Using cwebp for WebP conversion
cwebp -q 80 input.jpg -o output.webp

# Tools: TinyPNG, ImageOptim, sharp (Node.js)

Lazy Loading Images

<!-- Native lazy loading (supported in modern browsers) -->
<img 
  src="image.jpg" 
  loading="lazy"
  alt="Description"
>

<!-- With Intersection Observer for more control -->
<img 
  data-src="image.jpg"
  class="lazy-image"
  alt="Description"
>

<script>
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      observer.unobserve(img)
    }
  })
})

document.querySelectorAll('.lazy-image').forEach(img => {
  observer.observe(img)
})
</script>

JavaScript Optimization

Code Splitting

Modern bundlers enable splitting code into smaller chunks loaded on demand:

// Dynamic imports for code splitting
const HeavyComponent = React.lazy(() => import('./HeavyComponent'))

// Use with Suspense for loading states
<Suspense fallback={<Loading />}>
  <HeavyComponent />
</Suspense>

// Route-based splitting with Next.js
import dynamic from 'next/dynamic'
const Dashboard = dynamic(() => import('./dashboard'), {
  loading: () => <LoadingSpinner />,
  ssr: false, // optional: disable server-side rendering for this component
})

Tree Shaking

Remove unused code from your bundles by using ES6 modules:

// Good - tree shaking works
import { sortBy, groupBy } from 'lodash-es'

// Bad - whole library included
import _ from 'lodash'

// Build tools remove unused exports
// Only sortBy and groupBy are included in final bundle

Minification and Compression

# gzip compression (90%+ browser support)
# Configured in most web servers

# Brotli compression (better compression than gzip)
# Growing browser support

# Enable in your web server:
# Nginx: gzip on; gzip_types text/css application/javascript;
# Apache: mod_deflate enabled

CSS Optimization

Critical CSS

Inline CSS needed for above-the-fold content to reduce render-blocking:

<!-- Inline critical CSS in head -->
<style>
  /* Critical CSS for hero section */
  .hero { ... }
  .nav { ... }
</style>

<!-- Load rest asynchronously -->
<link rel="preload" href="styles.css" as="style">
<link rel="stylesheet" href="styles.css">

Font Optimization

/* Use font-display to optimize font loading */
@font-face {
  font-family: 'CustomFont'
  src: url('font.woff2') format('woff2')
  font-display: swap; /* Show fallback immediately, swap when custom loads */
}

/* Preload critical fonts */
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

Caching Strategies

HTTP Caching Headers

# Static assets (images, CSS, JS with hashes)
Cache-Control: public, immutable, max-age=31536000

# HTML and dynamic content
Cache-Control: public, max-age=0, must-revalidate

# API responses
Cache-Control: private, max-age=3600

# Don't cache
Cache-Control: no-cache, no-store, must-revalidate

Service Worker Caching

// Cache-first strategy for static assets
self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(
      caches.match(event.request).then((response) => {
        return response || fetch(event.request).then((response) => {
          return caches.open('images-v1').then((cache) => {
            cache.put(event.request, response.clone())
            return response
          })
        })
      })
    )
  }
})

// Network-first strategy for API calls
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      fetch(event.request).then((response) => {
        return caches.open('api-v1').then((cache) => {
          cache.put(event.request, response.clone())
          return response
        })
      }).catch(() => {
        return caches.match(event.request)
      })
    )
  }
})

Database and API Optimization

Pagination

Always paginate large datasets to avoid slow API responses:

// Good - paginated response
GET /api/posts?page=1&limit=20
{
  data: [...], // 20 items
  total: 500,
  page: 1,
  hasMore: true
}

// Bad - fetching everything
GET /api/posts  // Returns all 500 items!

Query Optimization

// Bad - N+1 query problem
const users = await db.user.findMany()
for (const user of users) {
  user.posts = await db.post.findMany({ where: { userId: user.id } })
  // Queries: 1 + N (one for each user!)
}

// Good - use include/select for eager loading
const users = await db.user.findMany({
  include: { posts: true }
})
// Queries: 1-2 database queries total

Response Compression

// Only return needed fields
const users = await db.user.findMany({
  select: { id: true, name: true, email: true }
  // Reduces response size by excluding large fields
})

// Paginate API responses
const posts = await db.post.findMany({
  skip: (page - 1) * pageSize,
  take: pageSize
})

Monitoring and Measuring Performance

Web Vitals Measurement

// Use web-vitals library
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'

getCLS(console.log) // Cumulative Layout Shift
getFID(console.log) // First Input Delay (deprecated, use INP)
getFCP(console.log) // First Contentful Paint
getLCP(console.log) // Largest Contentful Paint
getTTFB(console.log) // Time to First Byte

Performance Observer API

// Monitor performance metrics
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.duration}ms`)
  }
})

observer.observe({ entryTypes: ['navigation', 'resource', 'paint', 'largest-contentful-paint'] })

Tools for Measurement

  • Google PageSpeed Insights: Free analysis with recommendations. Shows real user data.
  • Lighthouse: Built into Chrome DevTools. Audits performance, accessibility, and SEO.
  • WebPageTest: Detailed performance waterfall charts. Free and detailed analysis.
  • Real User Monitoring (RUM): Collect actual user performance data using web-vitals library.
  • Sentry/DataDog: Performance monitoring and error tracking.

Performance Checklist

  • ☐ Optimize images (use WebP, responsive, lazy load)
  • ☐ Minimize JavaScript (code splitting, tree shaking)
  • ☐ Optimize fonts (preload, font-display: swap)
  • ☐ Inline critical CSS
  • ☐ Enable gzip/brotli compression
  • ☐ Configure proper cache headers
  • ☐ Set up Service Worker for offline support
  • ☐ Paginate API responses
  • ☐ Optimize database queries (avoid N+1)
  • ☐ Monitor Core Web Vitals regularly
  • ☐ Test on slow networks (Chrome DevTools throttling)
  • ☐ Use a CDN for static assets

Real-World Performance Improvements

Case Study: E-commerce site with 8-second load time:

  • Image optimization (WebP, responsive): -3s
  • Code splitting: -1.5s
  • Service Worker caching: -2s (return visits)
  • Database query optimization: -1s
  • Result: 2-second load time (75% improvement!)

Conclusion

Web performance optimization is an ongoing process that compounds over time. By understanding Core Web Vitals, implementing optimization techniques systematically, and measuring regularly, you can dramatically improve user experience and SEO rankings. Remember: every 100ms of improvement can lead to increased conversions. Start measuring today, identify bottlenecks with Lighthouse or PageSpeed Insights, and implement improvements incrementally. The best optimization you can do is the one you actually measure and validate.

Tags

#Performance#Web Development#SEO#Optimization

Share this article

About the Author

P

Prasanna Joshi

Expert Writer & Developer

Prasanna Joshi is an experienced software engineer and technology writer passionate about helping developers master modern web technologies. With years of professional experience in full-stack development, system design, and best practices, they bring real-world insights to every article.

Specializing in Next.js, TypeScript, Node.js, databases, and web performance optimization. Follow for more in-depth technical content.

Stay Updated

Get the latest articles delivered to your inbox