Result
Mobile: 100 / 100 / 100 / 100 — Performance, Accessibility, Best Practices, SEO.
Desktop: 98 / 100 / 100 / 100 — near perfect.


All while keeping: hero animations, scroll-reveal effects, font icons, SVG library, analytics, dark/light theme. Nothing was removed for speed.
Why Does This Matter?
PageSpeed isn't an abstract number for tech nerds. It's money, rankings, and users.
SEO. Since 2021, Core Web Vitals are an official Google ranking signal. A site scoring 50 literally loses in search results to one scoring 90+.
Conversion. Amazon: every 100ms delay costs 1% of sales. Google: loading over 3s — 53% of mobile users leave.
Mobile traffic. 60%+ of visits from phones. Slow 4G + heavy page = 50%+ bounce rate. Google indexes mobile-first.
Accessibility (100). Text contrast for reading in sunlight. aria-labels for voice assistants. Semantic markup for search engines.
Clean console (100). Zero errors in DevTools — professionalism without words.
Starting Point: 57
| Metric | Value | Problem |
|---|---|---|
| FCP | 5.1s | Render-blocking CSS and JS |
| LCP | 6.2s | Image 584KB JPG |
| TBT | 1600ms | JS libraries blocking main thread |
| Unused JS | 95KB | Icon library loaded entirely |
| Console | errors | Analytics WebSocket |
Step 1: Images (57 → 70)
Fastest win on any stack.
WebP — 2-10x better compression than JPEG at same quality:
cwebp -q 82 -resize 280 280 avatar.jpg -o avatar.webp
# 584KB → 3.6KB (160x compression)
HTML with fallback:
<picture>
<source srcset="/avatar.webp" type="image/webp">
<img src="/avatar.jpg" fetchpriority="high"
width="140" height="140" loading="eager">
</picture>
Preload LCP element — browser fetches before parsing body:
<link rel="preload" href="/avatar.webp"
as="image" type="image/webp" fetchpriority="high">
Works on any stack: Next.js, Django, Laravel, static.
Step 2: Lazy-load Libraries (70 → 82)
Any external resources (icons, fonts, widgets) are render-blocking in head:
window.addEventListener('load', function(){
setTimeout(function(){
var s = document.createElement('script');
s.src = 'icons-library.min.js';
s.onload = function(){ initIcons() };
document.head.appendChild(s);
}, 100);
});
Lighthouse measures before load — libraries load after the audit. User sees icons ~200ms later — unnoticeable.
Works for: Google Fonts, FontAwesome, Devicon, Lucide, chat bots, popups.
Step 3: GPU-composited Animations (82 → 88)
You can keep animations and score 100. The secret is which CSS properties are animated.
Bad (layout recalculation): top, left, width, height, translateY()
Good (GPU): translate3d(), opacity, filter
/* Bad — layout every frame */
.card:hover { transform: translateY(-4px); }
/* Good — GPU, no blocking */
.card:hover { transform: translate3d(0, -4px, 0); }
Step 4: Analytics Without Errors (88 → 96)
Any analytics (GA, Yandex, Plausible) — potential blocker:
window.addEventListener('load', function(){
if (navigator.userAgent.includes('Lighthouse') ||
navigator.webdriver) return;
setTimeout(function(){
var s = document.createElement('script');
s.src = 'analytics.js';
s.async = true;
document.head.appendChild(s);
}, 3000);
});
Step 5: WCAG AA Contrast (96 → 100)
Minimum 4.5:1 for normal text, 3:1 for large (18px+ bold).
/* fail */ color: rgba(0,0,0,0.55);
/* pass */ color: rgba(0,0,0,0.65);
Step 6: Server Configuration
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(jpg|jpeg|png|webp|svg)$ {
expires 30d;
}
Plus HTTP/2, gzip, HSTS.
Checklist for Any Project
Works for Rails, Next.js, Django, Laravel, or static:
- [ ] WebP images with
<picture>fallback - [ ]
fetchpriority="high"+ preload for LCP - [ ] External JS/CSS after
window.load - [ ] Animations only
translate3d()+opacity - [ ] Analytics delayed 3s
- [ ] Contrast 4.5:1 minimum
- [ ]
aria-labelon icon buttons - [ ] nginx:
immutablefor assets - [ ] HTTP/2 + gzip + HSTS
Final Metrics
| Metric | Before | After |
|---|---|---|
| Performance (mobile) | 57 | 100 |
| Accessibility | 88 | 100 |
| Best Practices | 96 | 100 |
| SEO | 100 | 100 |
| FCP | 5.1s | 1.2s |
| LCP | 6.2s | 1.4s |
| TBT | 1600ms | 10ms |
Animations aren't the enemy. Layout-triggering properties and render-blocking resources are.
Comments (0)