Website Performance Optimization
This guide covers optimizing your website performance: Core Web Vitals, image optimization and caching, each with practical code examples.
Why website performance matters
Website performance has been an official Google ranking factor since 2021. With Core Web Vitals, Google defined clear metrics that make user experience measurable. A slow website loses rankings, visitors and revenue.
In competitive markets, performance can decide whether a website succeeds or fails. A fast website helps your SEO and has a direct effect on revenue.
Largest Contentful Paint (LCP)
LCP measures how long it takes for the largest visible content element in the viewport to load. This is often a hero image, large heading, or video. Google recommends an LCP of 2.5 seconds or less.
Benchmarks
Optimization measures
1. Optimize server response time
Google's rule of thumb for TTFB (Time to First Byte) is 0.8 seconds or less. To get there, we recommend a fast hosting provider.
# Nginx Performance Tuning
worker_processes auto;
worker_connections 4096;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
# Caching
proxy_cache_path /var/cache/nginx levels=1:2
keys_zone=my_cache:10m max_size=1g
inactive=60m use_temp_path=off; 2. Prioritize resources with preload
Preload critical resources such as hero images and important fonts.
<!-- Preload critical resources -->
<link rel="preload" as="image" href="/hero-image.webp"
type="image/webp" fetchpriority="high">
<link rel="preload" as="font" href="/fonts/main.woff2"
type="font/woff2" crossorigin>
<!-- Preconnect to external domains -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://analytics.example.com"> 3. Optimize image delivery
Use modern image formats (WebP, AVIF) and responsive images with srcset.
<picture>
<!-- AVIF for modern browsers -->
<source type="image/avif"
srcset="hero-400.avif 400w,
hero-800.avif 800w,
hero-1200.avif 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1200px) 800px,
1200px">
<!-- WebP Fallback -->
<source type="image/webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1200px) 800px,
1200px">
<!-- JPEG Fallback -->
<img src="hero-800.jpg"
alt="Hero Image"
loading="eager"
fetchpriority="high"
width="1200"
height="600">
</picture> Interaction to Next Paint (INP)
Status October 2026: INP (Interaction to Next Paint) has been the Core Web Vital for responsiveness since 12 March 2024 and replaced FID. INP counts every interaction in a visit, FID counted only the first. Lighthouse triggers no interactions, so it reports no INP. In the lab, Total Blocking Time is a rough stand-in. Reliable INP numbers come from field data such as PageSpeed Insights and Search Console.
INP measures how long the page takes to respond visibly to clicks, taps and key presses during the whole visit, and reports one of the slowest interactions. A good INP is 200 ms or less; above 500 ms counts as poor.
Optimization measures
1. Reduce JavaScript execution
Large amounts of JavaScript block the main thread, so split your code and lazy-load what you can.
// Dynamic Imports for code splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));
// React.lazy with Suspense
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
// Webpack Magic Comments for chunk names
import(/* webpackChunkName: "analytics" */ './analytics'); 2. Avoid long tasks
Tasks over 50ms block user interactions. Use Web Workers for compute-intensive operations.
// Main Thread
const worker = new Worker('heavy-calculation.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => {
updateUI(e.data);
};
// heavy-calculation.js (Worker)
self.onmessage = (e) => {
const result = performHeavyCalculation(e.data);
self.postMessage(result);
}; 3. Optimize third-party scripts
Load analytics, chatbots and other third-party scripts with async or defer.
<!-- Load asynchronously -->
<script async src="https://widgets.example.com/embed.js"></script>
<script defer src="/analytics.js"></script>
<script defer src="/libs/jquery.min.js"></script> Cumulative Layout Shift (CLS)
CLS measures the visual stability of a page. Elements that "jump" during loading make for a poor user experience. A good CLS is 0.1 or less.
Common CLS causes
The browser doesn't know the size, so it reserves no space
Content appears after initial rendering
FOIT (Flash of Invisible Text) shifts layout
Solutions
1. Always specify width and height
<img src="logo.png" alt="Logo" width="200" height="50">
<img src="hero.jpg" alt="Hero" style="aspect-ratio: 16/9; width: 100%;">
.hero-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
} 2. Reserve space for dynamic content
.ad-placeholder {
width: 100%;
height: 250px;
background: linear-gradient(90deg, #1c2333 25%, #2c3446 50%, #1c2333 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
} 3. Optimize font loading
/* font-display for better fallback */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* Shows fallback immediately */
font-weight: 400;
font-style: normal;
}
/* Preload critical fonts */
<link rel="preload" as="font"
href="/fonts/custom.woff2"
type="font/woff2"
crossorigin> Image optimization
Images often make up a large share of a page's total size, so optimizing them is one of the biggest performance levers.
Format comparison
| Format | Browser support | File size | Recommendation |
|---|---|---|---|
| AVIF | All current major browsers | Smallest | First choice for modern browsers |
| WebP | All current major browsers | Usually smaller than JPEG | Best fallback |
| JPEG | 100% | Baseline | Legacy support |
| PNG | 100% | Largest | Only for transparency |
Optimization tools
Sharp (Node.js)
const sharp = require('sharp');
// Convert image to multiple formats
await sharp('input.jpg')
.resize(1200, 800, { fit: 'cover' })
.webp({ quality: 80 })
.toFile('output-1200.webp');
await sharp('input.jpg')
.resize(1200, 800, { fit: 'cover' })
.avif({ quality: 70 })
.toFile('output-1200.avif'); Squoosh (web app)
For single images, the Squoosh web app at squoosh.app shows the original and the compressed version side by side. The Squoosh CLI is no longer in the repository, so use Sharp for batch jobs.
Caching strategies
Caching reduces server load and makes your site much faster for returning visitors.
Browser caching (.htaccess)
<IfModule mod_expires.c>
ExpiresActive On
# Images - 1 year
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/avif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
# CSS and JS - 1 month
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
# Fonts - 1 year
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
# HTML - 1 hour
ExpiresByType text/html "access plus 1 hour"
</IfModule> Service Worker caching
// service-worker.js
const CACHE_NAME = 'v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png'
];
// Install Event - Cache resources
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
);
});
// Fetch Event - Cache-First Strategy
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Cache hit - return cached response
if (response) return response;
// Fetch from network
return fetch(event.request);
})
);
}); CDN & edge computing
A Content Delivery Network (CDN) distributes your content across servers worldwide and delivers it from the geographically nearest location. We recommend a CDN with a strong presence in your target regions.
Cloudflare
- Free plan available
- DDoS protection included
- Workers for server-side rendering at the edge
BunnyCDN
- Pay-as-you-go pricing per GB of traffic
- Strong performance in Europe
- Image optimization as a paid add-on (Bunny Optimizer)
Hosting recommendations
We recommend hosting providers with servers in your target region or with global CDN coverage.
Hetzner
Servers in Germany (Nuremberg & Falkenstein)
- • Strong performance/price ratio in Germany
- • Data stays in Germany when you pick a German location
- • Short distance to visitors in Germany
DigitalOcean
Global datacenters (Frankfurt, London, NYC)
- • Developer-friendly platform
- • Managed databases and Kubernetes
- • Easy scaling and backups
Cloudflare Workers
Global edge network
- • Works well with Astro and Next.js
- • Automatic deployments via Git
- • Static files are served straight from the CDN
- • The free plan is enough for small projects
Print or save the guide
Print this guide as PDF or share it with your team.
Professional performance optimization
We measure your website with Lighthouse and PageSpeed Insights, fix what slows it down and aim for green Core Web Vitals. The initial consultation is free, and afterwards you receive an individual fixed-price quote.