First-Screen Performance 4.2s → 1.8s Practical Case

0 0

Introduction

Should a frontend architect master first-screen performance? My answer is yes— not to memorize LCP thresholds, but to build the engineering judgment of “where to spend 100ms between 4.2s and 1.8s”. Three reasons:

  1. **The 4.2s → 1.8s is core hard data.**This is large ERP refactor’s real data, proving you can land optimizations in production.
  2. **First-screen optimization ROI isn’t linear.**100ms on the network layer may be more valuable than 1000ms on JS — architects must rank priorities.
  3. **Optimization without monitoring is gambling.**PR claims “500ms faster” but no production data — architects must install LCP / FCP / INP monitoring.

This is post #15 of the Frontend Architecture Cultivation Path series. We skip specific code (no writing prefetch examples), use stage breakdown + real time data + ROI ranking to make the complete decision path of 4.2s → 1.8s optimization concrete. The next post deep-dives into runtime performance (virtual scrolling / Web Vitals long-tail optimization).

1. 4.2s Real Composition: 5-Stage Time Breakdown

User report: 4.2s first-screen (LCP)

Per-stage capture (Chrome DevTools / Lighthouse / Sentry Performance):

TTFB                  HTML download           CSS / fonts              JS execution           First-screen image
800ms                 400ms                  600ms                   1500ms                 900ms
= 800 + 400 + 600 + 1500 + 900 = 4200ms ✓

Architect’s mantra: must capture data before optimizing. All “feels slow” is guessing; Lighthouse + WebPageTest + Sentrytells you where the money goes.

2. 5-Stage ROI Ranking

Stage4.2sTargetSavedChangeROI
Network layer800ms80ms-720msCDN + HTTP/3⭐⭐⭐⭐⭐
HTML download400ms100ms-300msStreaming SSR + size reduction⭐⭐⭐⭐
CSS / fonts600ms150ms-450msCritical CSS inline + font-display: swap⭐⭐⭐⭐
JS execution1500ms700ms-800mscode-split + vendor chunk⭐⭐⭐
First-screen image900ms400ms-500msAVIF + preload + LQIP⭐⭐⭐⭐

After optimization: 80 + 100 + 150 + 700 + 400 = 1430ms ≈ 1.4s(actual 1.8s includes other overhead)

3. Stage 1: Network Layer TTFB from 800ms → 80ms

# nginx config: enable HTTP/3
listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400';
add_header QUIC-Status $quic_status;
// client preload critical resources
<link rel="preload" href="/api/config" as="fetch" crossorigin />
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="dns-prefetch" href="https://api.example.com" />

Production data: after CDN hit TTFB 80ms(Cloudflare / Fastly edge nodes), total LCP saves 720ms.

4. Stage 2: HTML Size + Streaming SSR

// Next.js / Nuxt streaming SSR
export default function Page() {
  // header returns immediately
  return (
    <html>
      <head><link rel="preload" href="/critical.css" as="style" /></head>
      <body>
        <Header />  {/* streams immediately */}
        <Suspense fallback={<Skeleton />}>
          <SlowComponent />  {/* waits for async data */}
        </Suspense>
        <Footer />
      </body>
    </html>
  );
}

Key techniques:

  • Critical CSS inline(avoid blocking)
  • Non-critical JS defer
  • Suspense streaming: user sees skeleton first, content inserts when data arrives

Production data: HTML volume from 80KB to 25KB (removed server-side state serialization), first-byte to render-complete 350ms → 100ms.

5. Stage 3: Critical CSS + Fonts

<!-- Critical CSS inline -->
<style>
  body { margin: 0; font-family: system-ui; }
  .header { height: 56px; background: #1a73e8; }
  /* only first-screen CSS */
</style>

<!-- Non-critical CSS async -->
<link rel="preload" href="/all.css" as="style" onload="this.rel='stylesheet'" />
/* Font optimization */
@font-face {
  font-family: 'Brand';
  src: url('/font.woff2') format('woff2');
  font-display: swap;     /* use system font immediately, swap when font loads */
  unicode-range: U+4E00-9FFF;  /* only download Chinese unicode */
}

Production data: FOIT (Flash of Invisible Text) gone, font load from 600ms to 150ms.

6. Stage 4: JS Execution Time

// Code splitting: route-level + component-level
const AdminPage = lazy(() => import('./AdminPage'));
const HeavyChart = lazy(() => import('./HeavyChart'));

// vendor separate chunk, long-term cache
// vite.config.ts
build: {
  rollupOptions: {
    output: {
      manualChunks: {
        vendor: ['react', 'react-dom', 'antd'],
      },
    },
  },
}

Production data: first-screen JS from 1500ms to 700ms(vendor chunk hits browser cache, main JS only 200KB).

7. Stage 5: Image Optimization (Biggest LCP Culprit)

<!-- 1. AVIF + WebP progressive fallback -->
<picture>
  <source srcset="/hero.avif" type="image/avif" />
  <source srcset="/hero.webp" type="image/webp" />
  <img src="/hero.jpg" alt="..." />
</picture>

<!-- 2. preload critical LCP image -->
<link rel="preload" href="/hero.avif" as="image" type="image/avif" />

<!-- 3. LQIP placeholder (prevents layout shift) -->
<img src="data:image/svg+xml;base64,..."  <!-- 1KB blurred image -->
     data-src="/hero.avif"
     class="lazyload" />

Production data: first-screen image from 900ms to 400ms(AVIF 50-70% smaller than JPG).

8. Key Monitoring: Make Optimization Quantifiable

// Performance monitoring reporting
import { onLCP, onFCP, onINP, onTTFB } from 'web-vitals';

onLCP(metric => sendToAnalytics({
  name: 'LCP',
  value: metric.value,
  page: location.pathname,
  rating: metric.rating,  // 'good' | 'needs-improvement' | 'poor'
}));

onINP(metric => sendToAnalytics({ name: 'INP', value: metric.value }));

Architect’s rule: all LCP / INP data must report to Sentry / self-built monitoring, PR optimization effects must have production data backing.

9. Pitfall Reminders (Senior Architects Please Read Carefully)

  1. **Don’t skip PR validation.**Every optimization PR must measure LCP change (production data + Lighthouse double validation), don’t ship based on feeling.
  2. **Don’t forget SEO impact.**After CDN + cache strategy changes, SEO crawlers must be able to run JS(SSR fallback).
  3. **Don’t test only on fast networks.**Company WiFi / 4G tests show 1.8s, user on 3G might see 8s. Chrome DevTools Network Throttling tests weak networks.
  4. **Don’t suddenly swap first-screen images.**After optimization CDN hit / miss gap is huge, must have LQIP placeholder.
  5. **Don’t ignore monitoring alerts.**After installing LCP / INP reporting, threshold alerts(LCP > 2.5s triggers Feishu / Slack), regressions caught immediately.

Summary

Six key facts that thread first-screen optimization together:

  • 5-stage time distribution: network / HTML / CSS / JS / images, must capture data before optimizing.
  • ROI ranking: network layer saves 720ms with best ROI, SSR / image optimization next.
  • Network layer: CDN + HTTP/3 + preload, TTFB from 800ms to 80ms.
  • CSS / fonts: inline critical CSS + font-display: swap, FOIT gone.
  • JS execution: code-split + vendor chunk, first-screen JS from 1500ms to 700ms.
  • Image optimization: AVIF + preload + LQIP, image load from 900ms to 400ms.
  • Key monitoring: web-vitals reporting to Sentry, all optimizations need production data backing.

Next post: Runtime performance — virtual scrolling / Web Vitals long-tail optimization, FinSpread large spreadsheet performance tuning case.

5 Key Interview Question Tracks

This section maps one-to-one with the article. Q1 ships with a complete answer as a model; the other four are for you to think through — each one is followed by an AI assistant button for a one-click detailed answer.

Q1 (Answer): What’s the breakdown approach for optimizing first-screen from 4.2s to 1.8s? How to rank ROI across stages?

A: 5-stage time breakdown (network 800ms / HTML 400ms / CSS 600ms / JS 1500ms / image 900ms) → ROI ranking: network layer (CDN + HTTP/3) saves 720ms with best ROI, HTML (streaming SSR + critical CSS inline) saves 300ms next, CSS / fonts (font-display: swap) saves 450ms, JS (code-split + vendor chunk) saves 800ms, image (AVIF + preload + LQIP) saves 500ms. After optimization: 80+100+150+700+400 = 1.43s ≈ 1.8s(includes other overhead). Interview tip: explain “network layer ROI is highest because CDN hit edge nodes ≈ 30ms, while JS optimization ceiling is limited by code size” — this is engineering trade-off insight.

Q2 (Think): TTFB is the ceiling of first-screen optimization. How did you use CDN + HTTP/3 to bring TTFB from 800ms to 80ms? What specifically changed?

Q3 (Think): JS bundle too large caused 1.5s first-screen. How to use code-split and vendor chunk to split?

Q4 (Think): Images are the biggest LCP culprit (900ms). How to optimize with AVIF + preload + LQIP?

Q5 (Think): After optimizing first-screen, how do you monitor for regressions? How do you ensure PRs don’t sneak LCP back up?

💡 Each question has an AI assistant button — one click gets you a detailed answer.

References

🔗 Original Link Share to reach more people

Comments