Web Performance Optimization: Improve Core Web Vitals

Performance By TryzTech Team
Web PerformancePerformance OptimizationCore Web VitalsFrontend OptimizationSpeed OptimizationWeb DevelopmentBest Practices

Introduction: Why Performance Matters

Web performance is not just a technical metric—it directly impacts user experience, conversion rates, and search engine rankings. A slow website frustrates users, increases bounce rates, and hurts your SEO. In today’s digital landscape, performance optimization is a critical responsibility for every web developer.

This guide covers practical ways to improve Core Web Vitals, from image optimization to caching and monitoring. If you are building with Astro, see our Astro tutorial for fast websites too.

Table of Contents

Why Performance Matters

Website performance affects multiple aspects of your business:

  • User Experience: Fast sites keep users engaged
  • SEO Rankings: Google prioritizes fast websites in search results
  • Conversion Rates: Studies show a 1-second delay reduces conversions by 7%
  • Mobile Users: Performance is crucial for mobile device users
  • Brand Reputation: Slow sites damage user trust

Core Web Vitals

Google uses Core Web Vitals to assess real-world user experience. Its three key metrics are LCP, INP, and CLS. Monitoring and improving them helps a website feel fast, responsive, and stable.

1. LCP (Largest Contentful Paint)

LCP measures when the largest visible element (text, image, or video) is painted to the screen. This metric is crucial because it indicates when users perceive that the main content has loaded, directly impacting perceived performance and user satisfaction.

Target: < 2.5 seconds

// Monitor LCP
new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });

How to improve LCP:

  • Optimize server response times
  • Remove render-blocking resources
  • Optimize CSS and JavaScript
  • Use CDN for faster content delivery

2. INP (Interaction to Next Paint)

INP measures how quickly a page provides visual feedback after a user interaction, such as a click, tap, or key press. Unlike FID, which only assessed the first interaction, INP considers interactions throughout the visit. A high value usually means JavaScript, rendering, or long tasks are blocking the main thread.

Target: ≤ 200ms at the 75th percentile

import { onINP } from 'web-vitals';

onINP(({ value }) => {
  console.log('INP:', value);
});

How to improve INP:

  • Reduce JavaScript execution time
  • Break up long tasks
  • Use web workers for heavy computation
  • Defer non-critical JavaScript

3. CLS (Cumulative Layout Shift)

CLS quantifies how much visible content shifts unexpectedly during page load. Layout shifts occur when elements move to accommodate newly loaded content (ads, images, iframes). High CLS values are frustrating for users and can lead to accidental clicks, making this metric essential for quality user experience.

Target: < 0.1

/* Prevent layout shift with fixed dimensions */
.image-container {
  width: 400px;
  height: 300px;
  aspect-ratio: 4/3;
}

img {
  width: 100%;
  height: auto;
}

How to improve CLS:

  • Set explicit dimensions for images and videos
  • Avoid inserting content above existing content
  • Use transform animations instead of changing layout properties

Image Optimization

Images typically account for the largest portion of page weight—often 50% or more. Optimizing images is one of the highest-impact performance improvements you can make. This section covers modern image formats, responsive delivery strategies, and compression techniques that maintain visual quality while minimizing file size.

Modern Image Formats

Modern image formats such as WebP and AVIF generally offer better compression than traditional JPEG and PNG. Offer AVIF first, WebP as the modern fallback, and JPEG/PNG as the final fallback so browsers can choose the best supported format.

<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Description">
</picture>

Responsive Images

<!-- Serve different sizes for different devices -->
<img 
  srcset="
    small.jpg 480w,
    medium.jpg 800w,
    large.jpg 1200w,
    xlarge.jpg 1600w
  "
  src="medium.jpg"
  sizes="(max-width: 600px) 100vw, 
         (max-width: 1000px) 80vw,
         1000px"
  alt="Responsive image description"
>

Lazy Loading

Lazy loading defers off-screen images until the user approaches them. This reduces initial load time and bandwidth use, especially on long pages. Use it only for images outside the initial viewport; hero and LCP images should load normally so they do not delay the main content.

<!-- Native lazy loading -->
<img src="image.jpg" loading="lazy" alt="Description">

Always provide width and height (or an aspect-ratio) so the browser can reserve space before an image loads and avoid increasing CLS.

Image Compression Tools

You can compress images through a self-hosted pipeline, a cloud-based service, or a desktop application. The right choice depends on asset volume, dynamic-transformation needs, cost, and image-privacy requirements. Local or CI/CD pipelines give you full control and reproducible output, while cloud-based services simplify automated optimization and delivery.

Self-Hosted Options

ImageMagick

ImageMagick is a flexible choice for resizing, stripping metadata, and converting formats. This command caps the image width at 1600px, fixes EXIF orientation, removes metadata, and writes a smaller JPEG:

magick input.jpg -auto-orient -resize '1600x1600>' -strip -interlace Plane -quality 82 output.jpg

Create a WebP version with a quality setting tailored to the image type:

magick input.jpg -auto-orient -resize '1600x1600>' -strip -quality 78 output.webp

Run magick against copies or an output directory rather than overwriting originals. Review photographs, illustrations, and images containing text visually, since their best quality settings can differ.

Other Local Pipeline Options
  • Sharp (Node.js/libvips): fast for Node.js build pipelines and generating responsive variants. Use jpeg({ quality: 82, mozjpeg: true }), webp({ quality: 78 }), or avif({ quality: 50 }) for the target format.
  • libvips (vipsthumbnail): a lightweight, fast CLI for thumbnails and large batches of responsive sizes.
  • cwebp and avifenc: dedicated encoders from the WebP and libavif projects, useful when you need direct control over modern-format encoding.
  • mozjpeg, pngquant, and oxipng: JPEG- and PNG-focused optimizers. pngquant is lossy, while oxipng preserves pixel data.
  • SVGO: safely cleans SVGs in a local pipeline, for example npx svgo -f src/assets/icons.

A simple ImageMagick batch command for a separate output directory looks like this:

mkdir -p public/images/optimized
magick mogrify -path public/images/optimized -format jpg -auto-orient -resize '1600x1600>' -strip -interlace Plane -quality 82 public/images/source/*.{jpg,jpeg}

For production, make this a build script or CI job, keep generated files separate from source assets, and measure file sizes before and after optimization. Continue serving responsive sizes and WebP/AVIF: compression alone does not replace a sound image-delivery strategy.

Cloud-Based and Desktop Options

  • TinyPNG/TinyJPG: convenient for manual or API-based PNG/JPEG compression, particularly for small to medium volumes.
  • Cloudinary and similar image CDNs: resize, reformat, and adjust quality while serving images; useful when you need dynamic transformations.
  • Squoosh: useful for comparing visual quality and file size manually before choosing pipeline settings.
  • ImageOptim: a macOS desktop application for manual asset optimization.

Choose a self-hosted option when assets must remain within your infrastructure or the build output must be fully reproducible. A cloud-based option is convenient for on-demand transformations and CDN delivery, but account for cost, asset privacy, and service dependency.

Code Splitting & Lazy Loading

Code splitting breaks your JavaScript bundle into smaller chunks that load on-demand rather than sending everything upfront. This dramatically reduces initial load time, especially for single-page applications with many features. Combined with route-based splitting and dynamic imports, you can achieve near-instant page loads.

Dynamic Imports

Dynamic imports allow you to load JavaScript modules on-demand using the import() function. This is perfect for components that aren’t needed immediately, such as modals, settings panels, or advanced features that only some users will access. const HeavyComponent = lazy(() => import(’./HeavyComponent’));

function App() { return ( <Suspense fallback={}> ); }


### Route-Based Code Splitting

Route-based code splitting separates a bundle by route, so users download only the JavaScript needed for the current page. It is especially effective for applications with many pages or features.

```javascript
// Next.js example
import dynamic from 'next/dynamic';

const DynamicPage = dynamic(() => import('../pages/expensive-page'), {
  loading: () => <p>Loading...</p>
});

export default function App() {
  return <DynamicPage />;
}

Bundle Analysis

Understanding what’s in your bundle is crucial for optimization. Bundle analysis tools visualize your code to identify large dependencies, duplicated modules, and opportunities for splitting. Regular analysis helps catch performance regressions early in development.

# Analyze bundle size
npm install --save-dev webpack-bundle-analyzer

# Check bundle composition
webpack-bundle-analyzer dist/stats.json

Caching Strategies

Caching stores static and semi-static content closer to users, reducing server load and improving response times for repeat visitors. Strategic cache headers tell browsers and CDNs how long to cache content, providing the best balance between freshness and performance.

Browser Caching

Browser caching stores resources locally on users’ machines, eliminating the need to re-download them on subsequent visits. Set appropriate cache headers in your HTTP responses to control caching behavior and maximize performance gains for returning visitors.

Cache-Control: public, max-age=31536000

Cache duration guidelines:

  • HTML: 0 seconds (no-cache)
  • CSS/JS: 1 year (with content hash)
  • Images: 1 year
  • Fonts: 1 year
  • API responses: varies by content

Service Workers

// Basic service worker for offline support
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('v1').then(cache => {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/app.js'
      ]);
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

CDN Distribution

Use a Content Delivery Network (CDN) to serve static assets from servers closer to users:

  • Cloudflare
  • AWS CloudFront
  • Akamai
  • Fastly

Minification & Compression

JavaScript & CSS Minification

// Development
const bundle = 'console.log("Hello, world!"); const x = 42;';

// Production (minified)
const bundle = 'console.log("Hello, world!");const x=42;';

Compression Algorithms

# Enable gzip compression in nginx
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1024;
gzip_compression_level 6;

Tree Shaking

Remove unused code during bundling:

// unused.js
export function used() { return 'used'; }
export function unused() { return 'unused'; }

// app.js
import { used } from './unused';
console.log(used()); // unused() will be tree-shaken

Font Optimization

Fonts can significantly impact performance if not optimized.

Font Loading Strategy

/* Preload critical fonts */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');

/* Font-display strategies */
@font-face {
  font-family: 'MyFont';
  src: url('font.woff2') format('woff2');
  font-display: swap; /* Show fallback immediately */
}

Web Font Best Practices

  • Limit font families (2-3 maximum)
  • Load only needed font weights
  • Use variable fonts to reduce file size
  • Subset fonts to remove unused characters

Performance Monitoring Tools

Google Lighthouse

Built into Chrome DevTools, provides audits for performance, accessibility, and SEO.

# CLI usage
npm install -g lighthouse
lighthouse https://example.com --view

PageSpeed Insights

Analyzes pages and provides optimization suggestions: https://pagespeed.web.dev

WebPageTest

Detailed performance testing from multiple locations: https://www.webpagetest.org

Real User Monitoring (RUM)

// Collect metrics from real users
import('web-vitals').then(({ onCLS, onINP, onLCP }) => {
  onCLS(console.log);
  onINP(console.log);
  onLCP(console.log);
});

Best Practices Checklist

Performance optimization checklist for every project:

  • ✅ Measure and monitor Core Web Vitals
  • ✅ Optimize INP rather than FID
  • ✅ Optimize and serve modern image formats
  • ✅ Implement code splitting for large bundles
  • ✅ Configure browser and server caching
  • ✅ Enable gzip/brotli compression
  • ✅ Minify CSS and JavaScript
  • ✅ Defer non-critical JavaScript
  • ✅ Use a CDN for static assets
  • ✅ Optimize fonts and font loading
  • ✅ Remove render-blocking resources
  • ✅ Test performance with Lighthouse
  • ✅ Set up performance monitoring

Conclusion

Web performance optimization is an ongoing process. Start with images and LCP, measure INP and CLS with real-user data, then fix the bottlenecks with the greatest impact. For offline caching implementation, continue with our PWA, service worker, and caching strategy guide.

Remember: performance is a feature, not an afterthought. Prioritize it from the start of your project, and your users will thank you with better engagement and conversions.

Keep reading within the same topic.

Don't Miss Out

Get the latest tech articles, tips, and insights delivered to your inbox.