Let’s be honest: users have zero patience. If your beautiful new website takes more than 2 seconds to load, your users aren't marveling at your custom animations—they’re hitting the back button.
High-performance web design isn’t something you sprinkle on top of a project right before launch. It is a fundamental design philosophy.
Whether you’re building with raw HTML/CSS, React, Astro, or a headless CMS, here is the ultimate 7-step roadmap to achieving a 100% Lighthouse score without sacrificing UX.
1. Budget Your Performance Before You Design
You wouldn’t build a house without a financial budget, so don’t build a website without a performance budget.
Before sketching wireframes, set hard limits on your assets. If you know your target is a 1.5-second load time on a 3G network, your budget might look like this:
| Asset Type | Maximum Budget Size |
|---|---|
| Total Page Weight | < 1.5 MB |
| JavaScript | < 150 KB (Gzipped) |
| CSS | < 50 KB |
| Fonts | < 2 variants (WebP/WOFF2) |
Pro-Tip: Present this budget to your design team or client early. If they want a 50MB 4K video background on the homepage, point to the budget and explain the conversion loss.
2. Master the "Content-First" Layout (Kill the Layout Shifts)
Ever tried to click a button, only for the page to jump and cause you to click an ad instead? That’s a bad Cumulative Layout Shift (CLS), and Google penalizes it heavily.
High-performance design requires reserving space for dynamic content.
- Always explicitly state
widthandheightaspect ratios on images and video wrappers. - Avoid inserting dynamic banners or ads above existing content.
/* Prevent layout shifts by securing space for an image aspect ratio */
.hero-image {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
background: #f0f0f0; /* Skeleton placeholder */
}
3. The Holy Grail of Media: Next-Gen Formats & Sizing
Images are almost always the heaviest part of a webpage. Stop serving unoptimized PNGs or JPGs straight from Figma.
-
Use Modern Formats: Convert everything to
WebPorAVIF. AVIF can reduce file sizes by up to 50% compared to JPEG without visible quality loss. -
Responsive Images: Use
<picture>andsrcsetto serve smaller images to mobile devices and larger images to desktops.
<picture>
<source srcset="hero-large.avif" media="(min-width: 1024px)" type="image/avif">
<source srcset="hero-medium.webp" media="(min-width: 768px)" type="image/webp">
<img src="hero-fallback.jpg" alt="High performance dashboard preview" loading="lazy">
</picture>
Note: Do not lazy-load your Largest Contentful Paint (LCP) image (usually the main hero image). Load it instantly, and lazy-load everything below the fold.
4. Subsetting and Preloading Fonts
Custom typography makes a brand stand out, but loading five different weights of a Google Font will kill your performance.
- Limit Variant Weights: Stick to just 2 weights (e.g., Regular 400 and Bold 700).
- Use WOFF2: It features superior compression.
-
Font Display Swap: Use
font-display: swap;in your@font-facerule so the browser displays a system font while the custom font downloads, preventing a Flash of Invisible Text (FOIT).
@font-face {
font-family: 'CustomFont';
font-style: normal;
font-weight: 400;
src: url('/fonts/custom-font.woff2') format('woff2');
font-display: swap;
}
5. Write "Defensive" CSS & Eliminate Bloat
Frameworks like Tailwind CSS are fantastic because they inherently limit your CSS file size by purging unused styles. If you are writing custom CSS:
- Embrace CSS Grid and Flexbox: They reduce the need for deeply nested, complex HTML markup that slows down the browser's rendering engine.
-
Hardware-Accelerated Animations: Stick to animating
transformandopacity. Animating properties likewidth,height, ortopforces the browser to recalculate the entire page layout on every single frame.
/* DO THIS ✅ (Smooth, hardware-accelerated) */
.card:hover {
transform: translateY(-10px);
}
/* AVOID THIS ❌ (Forces expensive layout recalculation) */
.card:hover {
top: -10px;
}
6. The JavaScript Diet: Code-Splitting & Deferring
JavaScript is the most expensive resource on the web because the browser has to download, parse, compile, and execute it.
-
Defer Non-Critical Scripts: Third-party scripts (like analytics, chat widgets, and heatmaps) should always be loaded using
deferorasyncso they don’t block the main thread during initial page load. - Component Lazy-Loading: If a user doesn't open a modal, the code for that modal shouldn't be loaded. Use dynamic imports to fetch heavy interactive features only when needed.
// Load a heavy component only when the user interacts
button.addEventListener('click', async () => {
const { initHeavyChart } = await import('./charts.js');
initHeavyChart();
});
7. Continuous Monitoring (Performance is a Journey)
You hit 100% on Lighthouse. Congratulations! But tomorrow, a content manager might upload a 12MB uncompressed image to the blog.
Performance degrades over time if it isn't monitored. Set up automated checks in your workflow:
- Use Lighthouse CI or PageSpeed Insights integrated into your GitHub actions to block pull requests that drop performance scores.
- Monitor real-user metrics (CrUX data) to see how your site actually performs for real people on older mobile devices, not just on your high-end developer laptop.
Summary Checklist for Your Next Project
- [ ] Set a performance budget (< 1.5MB total weight).
- [ ] Fix CLS by declaring explicit aspect ratios.
- [ ] Convert images to AVIF/WebP and lazy-load below-the-fold assets.
- [ ] Preload critical fonts and use
font-display: swap. - [ ] Build animations using
transformandopacity. - [ ] Defer third-party scripts and code-split heavy JS features.
- [ ] Automate performance testing in your CI/CD pipeline.
What is your go-to trick for keeping websites lightweight and fast? Let me know in the comments below!
Top comments (0)