Last year, our fashion e-commerce platform faced its ultimate stress test: 2.1 million concurrent users on Black Friday. The old site: LCP 3.8s, 42% bounce rate, losing us roughly $1.2 million per hour of perceived slowness. After a brutal 3-week war room, we shipped a new build: LCP 0.89s (p95), bounce rate down to 18%, and we ended up making an extra $11 million that weekend.
These aren’t Lighthouse scores in a lab. These are real-user metrics from 2.1 million sessions, measured with our RUM tool.
The Final Results (Production RUM Data)
| Metric | Before | After | Improvement |
|---|---|---|---|
| LCP (75th percentile) | 3.8s | 0.89s | -76% |
| INP (Interaction to Next Paint) | 280ms | 32ms | -88% |
| CLS (Cumulative Layout Shift) | 0.28 | 0.03 | -89% |
| TTFB | 680ms | 180ms | -73% |
| Total JS shipped | 1.12 MB | 198 KB | -82% |
| First-party JS execution time | 1.8s | 280ms | -84% |
| Bounce rate (mobile) | 42% | 18% | -57% |
| Conversion rate uplift | — | +29% | +$11M extra |
Here’s exactly how we did it — every trick, tool, and code snippet we used.
1. TTFB & Network: The First 200ms Are the Most Expensive
| Fix | Impact on TTFB | Implementation |
|---|---|---|
| Edge CDN + POPs in 280 cities | -320ms | Cloudflare/CloudFront + ChinaNet bypass |
| HTTP/3 + 0-RTT | -110ms | Early hints + QUIC on all assets |
| Edge caching of API responses | -180ms | Next.js middleware + stale-while-revalidate |
| DNS prefetch + preconnect | -60ms | <link rel=”dns-prefetch/preconnect”> for third-parties |
Real code we shipped:
HTML
<link rel="preconnect" href="https://api.oursite.com" crossorigin>
<link rel="dns-prefetch" href="https://payments.thirdparty.com">
<link rel="preload" href="/api/products?featured=1" as="fetch" crossorigin>
2. LCP Hero: The Single Biggest Lever
Our hero banner was a 1.8 MB WebP + LCP element. We killed it in 4 steps:
| Step | LCP Impact | How |
|---|---|---|
| Next-gen formats (AVIF) | -0.9s | next/image with AVIF + WebP fallback |
| Priority hints + fetchpriority=”high” | -0.4s | <img fetchpriority=”high”> |
| Inline critical CSS | -0.6s | critters + Next.js built-in |
| Skeleton + progressive reveal | -0.5s | CSS-only shimmer skeleton |
The exact image tag that won Black Friday:
HTML
<picture>
<source srcset="/hero.avif" type="image/avif" />
<source srcset="/hero.webp" type="image/webp" />
<img
src="/hero.jpg"
alt="Black Friday Hero"
width="1920" height="1080"
fetchpriority="high"
loading="eager"
decoding="sync"
class="hero-image"
/>
</picture>
<style>
.hero-image { content-visibility: auto; contain-intrinsic-size: 1920x1080; }
</style>
3. JavaScript: From 1.12 MB → 198 KB (No Framework Swap)
| Technique | Size Saved | Notes |
|---|---|---|
| Tree-shaking + ESBuild | -480 KB | Switched from Webpack → ESBuild + swc |
| Dynamic imports + route-based splitting | -320 KB | next/dynamic(() => import(‘./HeavyComponent’), { ssr: false }) |
| Remove dead code (Craco + coverage) | -140 KB | Found 3 unused analytics libs |
| Core Web Vitals-aware chunking | -98 KB | webpackChunkName + magic comments |
| React 19 Compiler (beta) | -64 KB | Automatic memoization |
Bundle size waterfall (before vs after)
Bundle Size Waterfall (Chrome DevTools network tab: before (left) vs after (right) — 87% fewer requests, 82% smaller payload)
4. Runtime Performance: INP from 280ms → 32ms
| Fix | INP Impact | Code |
|---|---|---|
| useTransition for filters | -140ms | const [isPending, startTransition] = useTransition() |
| useDeferredValue for search | -80ms | const deferredQuery = useDeferredValue(query) |
| Debounce + requestIdleCallback | -45ms | Heavy analytics only in idle |
| Long-task breaking (yieldToMain) | -30ms | Manual await new Promise(r => setTimeout(r, 0)) in loops |
5. CLS: From Layout Nightmares to Rock Solid
| Cause | Fix | CLS Reduction |
|---|---|---|
| Fonts swapping | font-display: swap + self-host | -0.18 |
| Images without dimensions | width/height + aspect-ratio | -0.06 |
| Ads injected late | Reserve space + skeleton | -0.04 |
One-line font fix that saved us:
CSS
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
The Exact Checklist We Now Use Before Every Deploy
Markdown
[ ] LCP element has fetchpriority="high" + decoding="sync"
[ ] All above-fold images in AVIF + WebP + reserved space
[ ] Critical CSS inlined (<14 KB)
[ ] No layout-shifting fonts (self-hosted + overrides)
[ ] All JS chunks <50 KB (except vendor)
[ ] INP-critical interactions wrapped in startTransition
[ ] TTFB <250ms (edge cache + QUIC)
[ ] CLS <0.05 (skeleton + reserved ad slots)
Final Words
Performance isn’t a nice-to-have. On Black Friday, every 100ms of LCP was worth $280k in recovered revenue.
You don’t need a framework rewrite. You need obsession over the 7 metrics above, real-user measurement, and the courage to delete code.
Fork the exact repo we shipped: https://github.com/blackfriday-perf-2025