Why Performance Is a Feature, Not an Afterthought
A one-second delay in page load time reduces conversions by 7% on average. That number comes from multiple studies over the years, and while the exact figure varies, the direction is consistent: slower sites lose visitors and revenue. Google uses performance metrics as ranking signals. Users have shorter attention spans than ever. And mobile connections in many parts of the world are still slow and unreliable.
Performance optimization is not about chasing a perfect Lighthouse score. It is about making your website usable and responsive for real people on real devices and real networks. This guide covers the techniques that make the biggest difference, organized by impact.
Image Optimization: The Biggest Win
Images typically account for 50-70% of a page's total weight. Optimizing images is almost always the single highest-impact performance improvement you can make.
Modern Formats: WebP and AVIF
JPEG and PNG are legacy formats. Modern alternatives offer dramatically better compression at equal or better quality:
| Format | Compression vs JPEG | Browser Support | Best For |
|---|---|---|---|
| WebP | 25-35% smaller | 97%+ (all modern browsers) | General use, photos, graphics |
| AVIF | 40-50% smaller | 92%+ (Chrome, Firefox, Safari 16.4+) | Photos, high-quality images |
| SVG | N/A (vector) | 100% | Icons, logos, illustrations |
How to Serve Modern Formats
Use the <picture> element to serve the best format each browser supports:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Description" width="800" height="600">
</picture>
The browser picks the first format it supports. Older browsers fall back to the JPEG.
Responsive Images
Serving a 2400px-wide hero image to a phone with a 375px-wide viewport wastes bandwidth. Use the srcset and sizes attributes to let the browser choose the right image size:
<img
srcset="image-400.webp 400w, image-800.webp 800w, image-1200.webp 1200w, image-2400.webp 2400w"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 1200px"
src="image-800.webp"
alt="Description"
width="2400" height="1600">
This means: on viewports up to 768px, the image takes full width; up to 1200px, half the viewport; above that, fixed at 1200px. The browser calculates which image size to download based on the viewport width and device pixel ratio.
Lazy Loading
Images below the fold don't need to load immediately. Native lazy loading is now supported by all modern browsers:
<img src="image.webp" alt="Description" loading="lazy" width="800" height="600">
Do not lazy-load above-the-fold images. Your hero image and any images visible without scrolling should load immediately. Lazy loading them delays LCP (Largest Contentful Paint) and hurts your Core Web Vitals score.
Image Compression
Even after choosing the right format, compression settings matter. A few guidelines:
- JPEG/WebP quality 75-85 is usually indistinguishable from 100% quality to human eyes but 40-60% smaller in file size.
- AVIF quality 60-70 achieves comparable visual quality to JPEG at 85.
- Always strip metadata. EXIF data, color profiles, and thumbnails can add 50-200KB per image for no user-visible benefit.
- Resize before compressing. Don't compress a 5000px image and serve it at 800px. Resize it to the maximum display size first.
Tools for Image Optimization
- Sharp (Node.js) - Fast, excellent quality. Great for build pipelines.
- Squoosh - Google's web-based image compression tool. Good for one-off optimizations.
- ImageOptim (macOS) - Drag-and-drop lossless compression.
- Cloudflare Polish / Image Resizing - Automatic image optimization at the CDN level.
CSS and JavaScript Optimization
Minification
Minification removes whitespace, comments, and renames variables to shorter names. It is a free win with zero visual impact.
- CSS: Lightning CSS, cssnano, or clean-css
- JavaScript: Terser, esbuild, or SWC
If you're using a modern build tool (Vite, webpack, Rollup), minification is typically enabled by default for production builds.
Bundling and Code Splitting
Bundling combines multiple files into fewer files, reducing HTTP requests. Code splitting breaks the bundle into chunks that load on demand.
The goal is to find the right balance:
- Too many small files = too many HTTP requests (though HTTP/2 mitigates this significantly).
- One giant bundle = users download code for pages they never visit.
- Route-based code splitting is the sweet spot for most applications: each page loads its own chunk, plus a shared chunk with common code.
Tree Shaking
Tree shaking removes unused code from your bundles. If you import one function from a library, you should only ship that function, not the entire library. Modern bundlers do this automatically for ES module imports, but it fails silently when libraries don't support it properly.
Check your bundle size with tools like webpack-bundle-analyzer or source-map-explorer. You might be surprised how much dead code is in your production bundle.
Critical CSS
CSS is render-blocking by default. The browser won't paint anything until it has downloaded and parsed all linked stylesheets. Critical CSS is the technique of inlining the CSS needed for above-the-fold content directly in the <head>, then loading the rest asynchronously.
Tools like critical (by Addy Osmani) can automate this. Alternatively, if your total CSS is under 15-20KB (after minification and compression), it may be faster to inline all of it rather than making a separate request.
Font Loading Strategies
Web fonts are a common performance bottleneck. A typical font file is 20-50KB per weight/style combination, and most sites load 3-6 font files (regular, bold, italic, etc.).
The FOIT and FOUT Problem
- FOIT (Flash of Invisible Text) - The browser hides text until the font loads. Default behavior in Chrome and Firefox (with a 3-second timeout).
- FOUT (Flash of Unstyled Text) - The browser shows text in a fallback font, then swaps to the web font when it loads.
FOUT is the better user experience. Text is visible immediately, even if the font hasn't loaded yet. Control this with font-display:
@font-face {
font-family: 'MyFont';
src: url('myfont.woff2') format('woff2');
font-display: swap;
}
Font Optimization Techniques
- Use WOFF2 only. WOFF2 has 97%+ browser support and is 30% smaller than WOFF. Drop older formats.
- Subset your fonts. If you only use Latin characters, don't ship CJK glyphs. Tools like
glyphhangerorpyftsubsetcan create subsets with only the characters you actually use. - Preload critical fonts.
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>tells the browser to start downloading the font early. - Self-host fonts. Google Fonts CDN is convenient but adds a DNS lookup and connection to a third-party origin. Self-hosting eliminates this overhead and gives you full control over caching and subsetting.
- Consider system fonts. If your brand allows it, using a system font stack (
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif) eliminates font loading entirely. Text renders instantly.
Server Response Time
All the frontend optimization in the world won't help if the server takes two seconds to generate the HTML. Server response time (Time to First Byte, or TTFB) should be under 200ms for most pages.
Common Causes of Slow TTFB
- Slow database queries. Unindexed queries, N+1 queries, or queries that scan full tables.
- No page caching. Generating each page from scratch on every request when the content hasn't changed.
- Server location. If your server is in the US and your users are in Switzerland, every request travels across the Atlantic and back.
- Overloaded shared hosting. Budget shared hosting packs hundreds of sites on one server. During peak times, performance suffers.
- Unoptimized backend code. Synchronous operations, memory leaks, inefficient algorithms.
Solutions
- Use a CDN. Content Delivery Networks cache your content at edge locations worldwide. Users get served from the nearest edge, cutting latency dramatically. For more on CDN benefits, see our article on CDN benefits and security.
- Implement page caching. For content that doesn't change on every request, serve a cached version. Full-page caching can reduce TTFB from 500ms+ to 10-50ms.
- Consider static generation. If your content doesn't need to be dynamic per-request, pre-render it as static HTML at build time. Static pages served from a CDN are as fast as it gets.
- Optimize database queries. Add indexes, eliminate N+1 queries, use query result caching.
Caching Strategies
Caching is the most effective performance optimization after initial content reduction. A properly configured caching strategy means returning visitors get near-instant page loads.
Browser Caching
Control browser caching with HTTP headers:
Cache-Control: public, max-age=31536000, immutable
This tells the browser to cache the resource for one year and never revalidate it. Use this for static assets with content-hash filenames (e.g., app.a1b2c3.js). When the file changes, the filename changes, and the browser fetches the new version.
For HTML pages that may change, use:
Cache-Control: public, max-age=0, must-revalidate
This makes the browser check with the server on every visit (using ETags or Last-Modified), but if the content hasn't changed, the server responds with 304 Not Modified, and the browser uses the cached version without re-downloading the content.
CDN Caching
CDN caching sits between the browser and your origin server. The CDN caches your content at edge nodes. The first user in a region triggers a request to your origin; subsequent users in that region get the cached copy.
Key CDN caching configuration:
- Cache static assets aggressively. Images, CSS, JS, fonts: cache for a year with immutable headers.
- Cache HTML with short TTLs or stale-while-revalidate. This serves the cached version immediately while fetching an update in the background.
- Purge on deploy. When you deploy new content, purge the CDN cache to ensure users get the latest version.
Service Worker Caching
Service workers give you programmatic control over caching at the client side. They can intercept fetch requests and serve responses from a local cache, enabling offline functionality and instant repeat page loads.
Common strategies:
- Cache First - Serve from cache, fall back to network. Best for static assets.
- Network First - Try the network, fall back to cache if offline. Best for API calls and dynamic content.
- Stale While Revalidate - Serve from cache immediately, update the cache from the network in the background. Best balance of speed and freshness for most content.
Libraries like Workbox (by Google) make service worker caching much easier to implement.
The Critical Rendering Path
Understanding how browsers render pages helps you make targeted optimizations. The critical rendering path is the sequence of steps the browser takes to convert HTML, CSS, and JavaScript into pixels on screen.
The Sequence
- HTML parsing - The browser parses the HTML and builds the DOM (Document Object Model).
- CSS parsing - The browser parses CSS and builds the CSSOM (CSS Object Model). This is render-blocking: nothing paints until all CSS is parsed.
- Render tree - The browser combines DOM and CSSOM into a render tree (only visible elements).
- Layout - The browser calculates the position and size of every element.
- Paint - The browser fills in pixels.
- Composite - Layers are composited together and displayed on screen.
What Blocks Rendering
- CSS in the <head> blocks rendering. The browser won't paint until it has all CSS. This is why minimizing CSS file size and inlining critical CSS matters.
- Synchronous JavaScript blocks HTML parsing. When the parser hits a
<script>tag, it stops parsing HTML, downloads the script, executes it, then continues. Usedeferorasyncattributes to prevent this. - Web fonts can block text rendering (FOIT). Use
font-display: swapto prevent this.
Script Loading Attributes
| Attribute | Download | Execution | Best For |
|---|---|---|---|
| None (default) | Blocks parsing | Immediately | Nothing (avoid this) |
async |
Parallel to parsing | When downloaded (blocks parsing) | Independent scripts (analytics, ads) |
defer |
Parallel to parsing | After HTML is fully parsed | Most application scripts |
Third-Party Scripts: The Performance Tax
Third-party scripts are one of the most common and least acknowledged performance problems. Analytics, chat widgets, A/B testing tools, advertising pixels, social media embeds, CRM integrations, consent management platforms. Each one adds weight, execution time, and often additional HTTP requests to external servers.
The Real Cost
We have audited sites where third-party scripts accounted for:
- 60%+ of total JavaScript execution time
- 300-500ms of additional load time
- 20+ additional HTTP requests
- Multiple layout shifts (CLS problems) from dynamically injected content
Mitigation Strategies
- Audit every third-party script. List them all. For each one, ask: do we actually use the data this collects? Is there a lighter alternative? Can we remove it?
- Delay non-critical scripts. Analytics and chat widgets don't need to load immediately. Load them after the main content is interactive (on
requestIdleCallbackor after a user interaction). - Self-host where possible. If you're loading a JavaScript library from a third-party CDN, consider self-hosting it. You get better caching control and eliminate a DNS lookup.
- Use facades for embeds. Instead of loading the full YouTube player on page load, show a thumbnail with a play button. Load the actual player only when the user clicks. Same principle for maps, chat widgets, and social embeds.
- Monitor with web-vitals. The
web-vitalslibrary reports real-user metrics. Set up monitoring to catch when a third-party script update degrades performance.
Measuring and Monitoring Performance
You cannot optimize what you don't measure. There are two types of performance data, and you need both.
Lab Data (Synthetic Testing)
Lab data comes from tools that simulate page loads under controlled conditions:
- Lighthouse (built into Chrome DevTools) - Comprehensive audits with actionable recommendations.
- WebPageTest - Detailed waterfall charts, multi-step testing, video capture, comparison views.
- PageSpeed Insights - Combines Lighthouse lab data with real-user data from CrUX.
Lab data is consistent and reproducible. It is good for debugging specific issues and comparing before/after changes. But it tests one device, one network, one location. It does not represent your actual users.
Field Data (Real User Monitoring)
Field data (RUM) comes from your actual users on their actual devices and connections:
- Chrome User Experience Report (CrUX) - Free, aggregated real-user data for any publicly accessible website. This is the data Google uses for ranking.
- web-vitals library - Lightweight JavaScript library that captures Core Web Vitals from real users.
- Commercial RUM tools - SpeedCurve, Calibre, Sentry Performance, Datadog RUM.
Core Web Vitals
Google's Core Web Vitals are the metrics that matter most for user experience and search ranking. For a deep dive, read our article on Core Web Vitals and SEO impact.
| Metric | What It Measures | Good | Needs Improvement | Poor |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | Loading speed | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | Responsiveness | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | Visual stability | ≤ 0.1 | ≤ 0.25 | > 0.25 |
A Performance Optimization Checklist
Here is a prioritized checklist based on what typically delivers the most impact:
- Optimize images. Convert to WebP/AVIF, use responsive images, lazy load below-the-fold images, compress appropriately.
- Enable compression. Brotli (preferred) or gzip for all text-based resources (HTML, CSS, JS, SVG, JSON).
- Set up proper caching. Long cache lifetimes for static assets with content hashing. Short or no cache for HTML.
- Use a CDN. Serve static assets from edge locations close to your users.
- Minimize render-blocking resources. Inline critical CSS, defer JavaScript, use font-display: swap.
- Reduce third-party scripts. Audit, delay, replace, or remove.
- Minify and bundle. Use a modern build tool with tree shaking.
- Optimize fonts. WOFF2 only, subset, preload critical fonts, consider system fonts.
- Improve server response time. Caching, CDN, efficient backend code, appropriate hosting.
- Set up monitoring. Track Core Web Vitals from real users, not just lab tests.
Wrapping Up
Website performance is not a one-time task. It requires ongoing attention as content changes, features are added, and third-party scripts are updated. The good news is that the fundamentals are stable: optimize images, cache aggressively, minimize blocking resources, and measure what matters.
Start with images and caching. Those two areas alone can cut page load time in half for many sites. Then work through the checklist above, prioritizing the items that apply to your specific situation.
If you need help optimizing your website's performance or want a professional audit, reach out to our team in Lugano. We help businesses across Switzerland build fast, secure websites that deliver real results.
Want to know if your site is secure?
Request a free security audit. In 48 hours you get a complete report.
Request Free Audit