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:
- Largest Contentful Paint (LCP): Measures loading performance. LCP marks the time when the largest visible content element (typically a hero image or heading block) finishes rendering. Target: under 2.5 seconds.
- Interaction to Next Paint (INP): Measures responsiveness. INP captures the latency of user interactions (clicks, taps, key presses) throughout the entire page lifecycle. It replaced First Input Delay (FID) as the responsiveness metric. Target: under 200 milliseconds.
- Cumulative Layout Shift (CLS): Measures visual stability. CLS quantifies how much the page layout shifts unexpectedly during loading. Target: under 0.1.
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:
- PageSpeed Insights: Enter your URL at pagespeed.web.dev. It shows both lab data (Lighthouse) and field data (CrUX) if available.
- Chrome DevTools Performance tab: Record a page load to see exactly what happens during rendering, including long tasks and layout shifts.
- web-vitals.js library: Add the lightweight web-vitals library to measure real user metrics and send them to your analytics.
<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:
- Use modern formats: WebP offers 25-35% smaller files than JPEG at equivalent quality. AVIF is even smaller but has less browser support.
- Serve responsive images with
srcsetandsizesattributes so mobile devices do not download desktop-sized images. - Compress aggressively. A quality setting of 75-80 in WebP is usually indistinguishable from the original.
- Set explicit
widthandheightattributes on every<img>tag. This also prevents layout shifts (helping CLS).
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:
- Inline critical CSS directly in the
<head>. This eliminates the network round-trip for above-the-fold styles. - Load non-critical CSS asynchronously using
<link rel="preload" as="style" onload="this.rel='stylesheet'">. - Remove unused CSS. Bootstrap 4.4.1 is roughly 160 KB uncompressed, but most pages use only a fraction of it.
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:
- HTML files:
Cache-Control: public, max-age=300(5 minutes). HTML changes most frequently, so keep the cache short. - CSS and JS files:
Cache-Control: public, max-age=31536000, immutable(1 year). Use cache-busting via query strings or filename hashing when you update these files. - Images:
Cache-Control: public, max-age=31536000, immutable(1 year). Images rarely change. - Fonts:
Cache-Control: public, max-age=31536000, immutable(1 year). Fonts essentially never change.
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:
- Brotli (
Content-Encoding: br) is supported by all modern browsers and should be preferred when available. - Gzip (
Content-Encoding: gzip) is the universal fallback. - For static hosting, you can pre-compress files at build time and serve the pre-compressed version, avoiding on-the-fly compression overhead.
CDN Benefits for Static Sites
A Content Delivery Network is arguably the single most impactful infrastructure change for a static site. CDNs provide:
- Geographic distribution: Files served from edge nodes close to the user, reducing latency from hundreds of milliseconds to single digits.
- Automatic compression: Most CDNs apply Gzip and Brotli compression automatically.
- DDoS protection: CDN infrastructure absorbs traffic spikes and malicious requests.
- HTTPS termination: Free TLS certificates and optimized TLS handshakes.
- HTTP/2 and HTTP/3: Modern protocols that allow multiplexed requests over a single connection.
Practical Checklist for Static Sites
Use this checklist to systematically improve your Core Web Vitals:
- Run PageSpeed Insights and note your current LCP, INP, and CLS scores.
- Add
widthandheightto every<img>and<iframe>tag. - Convert images to WebP format and compress to quality 75-80.
- Preload the LCP image with
<link rel="preload">. - Add
font-display: swapto all@font-facerules. - Move non-critical CSS to async loading or inline critical CSS.
- Add
deferorasyncto all non-critical script tags. - Reserve space for ad units with minimum height containers.
- Enable Gzip/Brotli compression on your server or CDN.
- Set appropriate
Cache-Controlheaders for each asset type. - Deploy behind a CDN if not already.
- 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.