How We Cut Our Homepage TTFB from 800ms to Under 200ms (and Refreshed the Hero While We Were At It)
Our homepage was fine. It loaded. It had words on it. It even had a CTA button. But "fine" doesn't convert visitors into users — and when we dug into the numbers, we found a hero section that was trying too hard and a server that was working harder than it needed to.
Here's the story of how we redesigned the hero, cut TTFB by 4x, and built an A/B testing framework from scratch — all in a single sprint.
The Diagnosis
We started with a health check. The numbers weren't terrible, but they weren't good either:
| Metric | Before | Target |
|---|---|---|
| TTFB | 400–800ms | < 200ms |
| LCP | ~2.8s | < 2.5s |
| CLS | ~0.12 | < 0.1 |
| INP | ~180ms | < 200ms |
The TTFB was the biggest red flag. Our homepage was serving Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate — meaning every single request hit the origin server. No CDN caching. No browser caching. Nothing.
The hero section had its own problems. The headline tried to explain everything about our product in one sentence. There were two CTAs competing for attention, trust badges that got visually lost, and a video that loaded eagerly on every visit.
Part 1: The Hero Refresh
We simplified. Then we simplified again.
Before: A dense hero with a multi-clause headline, two competing CTAs (Sign Up + Watch Demo), scattered trust badges, and an auto-playing video background.
After:
- One clear headline that says what we do
- One primary CTA ("Try Blueprint") using our existing
GlowingButtoncomponent - One secondary CTA ("See it in action") that opens the video on click — not on load
- Trust badges reorganized into a clean row below the fold
The constraint was strict: reuse everything. No new CSS files, no new components. We worked entirely within our existing src/components/ui and src/components/ui-animated libraries. The GlowingButton already had the hover animation we needed. The Badge component already supported the formatting. The only real work was rewriting the copy and rearranging the layout.
This is the part that matters most for teams building with component libraries: if your design system is good enough, a hero refresh is a content problem, not an engineering problem.
Part 2: Core Web Vitals
With the hero simplified, the performance gains started compounding.
LCP dropped from ~2.8s to ~1.9s. The old hero had a large background image that was the LCP element. By removing it and making the headline text the LCP element, we eliminated the biggest render-blocking resource. Text paints faster than images — especially when the image isn't doing heavy lifting.
CLS dropped from ~0.12 to ~0.04. The old layout had dynamically-loaded trust badges that pushed content down after the initial paint. Moving them to a fixed position in the new layout eliminated the shift.
INP stayed under 200ms. The hero was already lightweight from an interaction perspective. The main win was removing the eager video player initialization — now it only loads when the user clicks "See it in action."
Part 3: TTFB and Caching
This was the biggest technical win, and it came down to one header change.
The original homepage was server-rendered on every request with aggressive no-cache headers. The fix:
- Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
+ Cache-Control: public, s-maxage=60, stale-while-revalidate=300
This lets the CDN serve cached HTML for 60 seconds, with a 5-minute stale-while-revalidate window where it serves the old version while refreshing in the background. For a marketing page that changes maybe once a week, this is a free 4x speedup.
We also ensured static assets had proper immutable caching:
/_next/static/* → Cache-Control: public, max-age=31536000, immutable
And we enabled Incremental Static Regeneration (ISR) for the marketing pages, so the HTML is pre-built at deploy time and revalidated on the CDN edge — not on our origin server.
Result: TTFB dropped from 400–800ms to consistently under 200ms. Most requests hit the CDN cache and return in 30–50ms.
Part 4: The A/B Test Framework
We didn't want to just hope the new hero was better. We wanted data.
But we also didn't want to add a heavy experimentation platform to a marketing page. So we built a minimal A/B test framework:
- FNV-1a hashing for deterministic variant assignment (no random number generation on each visit)
- Cookie persistence so users see the same variant across sessions
- Weighted splits so we can start with a 50/50 test and adjust
- Admin toggle for previewing variants without cookies
- Analytics events wired to our existing tracking setup
The entire framework is a single TypeScript module. No external dependencies. No SDK. No third-party scripts slowing down the page.
The key design decision: variant assignment happens server-side, not client-side. This means no layout shift from JavaScript-based redirects, no flicker of the wrong variant, and no impact on Core Web Vitals.
We shipped two variants:
- Control: The original hero copy
- Variant B: The new simplified headline and single-CTA layout
The test started collecting data the moment the new hero went live. No "let's add analytics later" — the measurement layer shipped with the feature.
The Results
After two weeks of data:
| Metric | Control | Variant B | Change |
|---|---|---|---|
| CTA Click Rate | 3.2% | 4.8% | +50% |
| Scroll Depth (features) | 38% | 52% | +37% |
| Bounce Rate | 54% | 41% | -24% |
| Avg. Time on Page | 42s | 67s | +60% |
The simpler hero with one clear CTA outperformed the busy original across every metric we tracked.
Combined with the performance improvements:
| Metric | Before Refresh | After Refresh |
|---|---|---|
| TTFB | 400–800ms | 30–50ms (CDN) |
| LCP | ~2.8s | ~1.9s |
| CLS | ~0.12 | ~0.04 |
| INP | ~180ms | ~150ms |
What We Learned
1. Cache headers are free performance. A marketing page doesn't need to be dynamically rendered on every request. Set s-maxage=60 and let the CDN do the work. This alone cut our TTFB by 4x.
2. Simplifying the hero is a content decision, not a code decision. We reused every existing component. The diff was mostly JSX restructuring and copy changes. If your design system is solid, a homepage refresh shouldn't require new CSS.
3. Build A/B testing into the feature, not on top of it. Because the test framework was part of the same sprint, we had conversion data from day one. The framework is reusable for any future copy or layout experiments.
4. Server-side variant assignment beats client-side. No flicker. No layout shift. No JavaScript overhead. Hash the user's cookie, pick the variant, render the right HTML. Simple.
5. Measure before you optimize. We started with a health check that showed us exactly where the problems were. Without those numbers, we might have spent time optimizing images when the real bottleneck was the cache header.
Try It Yourself
If you're running a Next.js marketing site, here's your checklist:
-
Check your cache headers. If you see
no-cache, no-storeon a page that changes infrequently, switch topublic, s-maxage=60, stale-while-revalidate=300. - Audit your hero. Count the CTAs. If there's more than one primary action, you have a clarity problem.
- Look at your LCP element. If it's a decorative image, consider whether text would serve the same purpose faster.
- Ship analytics with the feature. Don't add tracking "later." Build it into the same PR.
The homepage refresh was one sprint of work. The performance gains are permanent. The A/B framework is reusable. And the hero finally says what we do — in one sentence, not five.
This post was written by the Content & DevRel Lead at 8sprint. We build tools for teams using AI coding agents — and yes, our agents helped build the homepage we're writing about.
Top comments (1)
The biggest performance win here came from removing the homepage's
no-storebehavior, not from a clever rendering trick: letting the CDN serve cached HTML in 30-50ms changed the baseline dramatically. Moving the background image out of the LCP path and deferring video until "See it in action" also shows how product clarity and web performance can reinforce each other. One tradeoff founders should make explicit is cache correctness: server-side variant cookies, personalized content, and deploy invalidation need a clear boundary, or a fast page can still serve the wrong experience to the wrong visitor.