Static websites have a natural performance advantage. Without server-side rendering, database queries, or application logic at request time, a static site should be fast by default. Yet many static sites still score poorly on Google's Core Web Vitals. Heavy images, render-blocking CSS, layout shifts from ads or web fonts, and unoptimized JavaScript all take their toll. The good news is that most issues are straightforward to fix, and you do not need complex build tooling to achieve excellent scores.

What Are Core Web Vitals?

Core Web Vitals are a set of metrics Google uses to measure real-world user experience on web pages. They directly influence search rankings. The three core metrics are:

These metrics are measured on real user devices via the Chrome User Experience Report (CrUX). Lab tools like PageSpeed Insights and Lighthouse provide estimates, but field data from real users is what counts for rankings.

Measuring Your Current Performance

Before optimizing, establish a baseline. Use these tools:

<script type="module">
import {onLCP, onINP, onCLS} from "https://unpkg.com/web-vitals@4/dist/web-vitals.js?module";

function sendToAnalytics(metric) {
    // Send to your analytics endpoint
    console.log(metric.name, metric.value);
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
</script>

Optimizing Largest Contentful Paint (LCP)

LCP is typically the most impactful metric to optimize for static sites. The LCP element is usually a hero image, a large heading, or a background image. The goal is to get that element painted as quickly as possible.

Preload critical resources. If your LCP element is an image, use a preload hint so the browser fetches it early, before it discovers the <img> tag in the HTML:

<link rel="preload" as="image" href="/img/hero.webp" />

Optimize images. Images are the most common LCP element and the biggest opportunity for improvement:

Reduce render-blocking CSS. CSS blocks rendering — the browser will not paint anything until all CSS in the <head> has been downloaded and parsed. For static sites, consider these approaches:

Minimize server response time. Even for static files, the Time to First Byte (TTFB) matters. Use a CDN to serve files from edge locations close to your users. Most CDN providers can serve static files with TTFB under 100ms globally.

Optimizing Interaction to Next Paint (INP)

INP measures how quickly your page responds to user interactions. For static sites with minimal JavaScript, this is often already good. But third-party scripts (ads, analytics, social widgets) can block the main thread and cause poor INP scores.

Defer non-critical JavaScript. Use the defer or async attribute on script tags. The defer attribute downloads the script in parallel but executes it after HTML parsing completes. The async attribute downloads and executes as soon as possible, which can be during HTML parsing:

<!-- Blocks parsing -- avoid for non-critical scripts -->
<script src="heavy-library.js"></script>

<!-- Downloads in parallel, executes after parsing -->
<script src="analytics.js" defer></script>

<!-- Downloads and executes as soon as ready -->
<script src="ad-script.js" async></script>

Break up long tasks. Any JavaScript task that runs for more than 50ms is considered a "long task" and can block user interactions. If you have intensive operations (like processing a large dataset for a tool page), break them into smaller chunks using setTimeout or requestIdleCallback:

function processInChunks(items, chunkSize, processFn, callback) {
    var index = 0;
    function nextChunk() {
        var end = Math.min(index + chunkSize, items.length);
        for (var i = index; i < end; i++) {
            processFn(items[i]);
        }
        index = end;
        if (index < items.length) {
            setTimeout(nextChunk, 0);
        } else if (callback) {
            callback();
        }
    }
    nextChunk();
}

Be careful with third-party scripts. Ad scripts and analytics are the most common cause of poor INP on static sites. Load them with async, and consider lazy-loading ad units that are below the fold.

Optimizing Cumulative Layout Shift (CLS)

Layout shifts are jarring for users. You are reading an article and suddenly the text jumps down because an ad loaded above it, or an image rendered without reserved space. CLS is often the metric where static sites score worst because of ads and web fonts.

Set explicit dimensions on images and iframes. Always include width and height attributes on <img> and <iframe> elements. The browser uses these to calculate the aspect ratio and reserve space before the resource loads:

<img src="photo.webp" width="800" height="600" alt="Description"
     style="max-width: 100%; height: auto;" />

The CSS max-width: 100%; height: auto; makes it responsive while maintaining the aspect ratio. Modern browsers support the aspect-ratio CSS property for even more control.

Handle web fonts properly. When a web font loads, the browser may re-render text, causing a layout shift. Use font-display: swap to show a fallback font immediately and swap in the web font when it loads. Better yet, choose a fallback font with similar metrics to minimize the visual change:

@font-face {
    font-family: "Lato";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/font/lato-regular.woff2") format("woff2");
}

Reserve space for ads. Ad units are a major cause of CLS because they load asynchronously and inject content. Set a minimum height on the ad container so space is reserved even before the ad loads:

.ad-container {
    min-height: 250px;  /* Match the expected ad height */
    background: #f5f5f5;
}

Never inject content above existing content. If you dynamically add banners, cookie notices, or notification bars, use fixed or sticky positioning so they overlay rather than push down existing content.

HTTP Caching Headers

Proper caching ensures returning visitors load your site almost instantly. For static sites, set aggressive cache headers:

Compression: Gzip and Brotli

Text-based assets (HTML, CSS, JS, SVG) compress extremely well. Gzip reduces file sizes by 60-80%, and Brotli achieves 10-15% better compression than Gzip. Most CDNs and web servers support both automatically. Ensure compression is enabled:

CDN Benefits for Static Sites

A Content Delivery Network is arguably the single most impactful infrastructure change for a static site. CDNs provide:

Practical Checklist for Static Sites

Use this checklist to systematically improve your Core Web Vitals:

  1. Run PageSpeed Insights and note your current LCP, INP, and CLS scores.
  2. Add width and height to every <img> and <iframe> tag.
  3. Convert images to WebP format and compress to quality 75-80.
  4. Preload the LCP image with <link rel="preload">.
  5. Add font-display: swap to all @font-face rules.
  6. Move non-critical CSS to async loading or inline critical CSS.
  7. Add defer or async to all non-critical script tags.
  8. Reserve space for ad units with minimum height containers.
  9. Enable Gzip/Brotli compression on your server or CDN.
  10. Set appropriate Cache-Control headers for each asset type.
  11. Deploy behind a CDN if not already.
  12. Re-run PageSpeed Insights and verify improvements.

Static sites are already halfway to excellent performance. The remaining work is mostly about eliminating unnecessary blocking resources, reserving space for dynamic content, and ensuring assets are properly compressed and cached. Each optimization on this list is achievable without any build system, bundler, or framework — just careful HTML, CSS, and server configuration.