Skip to content
AstroPaper
Go back

Critical Rendering Path (CRP) — Ultimate Deep-Dive Guide

Updated:
Edit page

Critical Rendering Path (CRP) — Ultimate Deep-Dive Guide

Table of Contents

Open Table of Contents

1. Big Picture

What Is the Critical Rendering Path?

The Critical Rendering Path (CRP) is the sequence of steps a browser must complete to convert HTML, CSS, and JavaScript into pixels on screen. It represents the minimum work required to render the first frame of a page.

WHY it matters: Every millisecond spent in the CRP delays the user seeing content. On mobile devices with constrained CPU/GPU/network, CRP inefficiency compounds into seconds of blank screen.

Problems CRP optimization solves:

Key Concepts Differentiated

ConceptWhat It IsWhen It Happens
ParsingConverting raw bytes (HTML/CSS/JS) into structured trees (DOM/CSSOM/AST)Network → Parser
RenderingThe full pipeline from DOM+CSSOM → pixels on screenAfter parse, before display
Style RecalculationRecomputing which CSS rules apply to which DOM nodesAfter DOM/CSS changes
Layout (Reflow)Computing geometry (position, size) of every visible elementAfter style recalc, on geometry changes
PaintFilling in pixels (colors, text, images, borders) into paint recordsAfter layout
CompositingCombining painted layers into final screen image (GPU-accelerated)After paint
HydrationAttaching JS event handlers to server-rendered HTMLClient-side, after SSR HTML arrives
ReflowRe-running layout due to geometry changes (expensive)DOM mutation, style change, resize
RepaintRe-painting without layout change (e.g., color change)Visual-only property changes

The Full Rendering Lifecycle

Network Request


┌─────────────────────────────────────────────────────┐
│  HTML Parser (incremental, byte-stream)             │
│  ├── Builds DOM tree                                │
│  ├── Preload Scanner (speculative fetches)          │
│  └── Encounters <link>, <script>, <style>           │
└─────────────────────────────────────────────────────┘
    │                    │                    │
    ▼                    ▼                    ▼
┌──────────┐      ┌──────────┐      ┌──────────────┐
│ CSS Parse │      │ JS Exec  │      │ Subresource  │
│ → CSSOM   │      │ (blocks  │      │ Fetches      │
│           │      │  parser) │      │ (preloaded)  │
└──────────┘      └──────────┘      └──────────────┘
    │                    │
    ▼                    ▼
┌─────────────────────────────────────────────────────┐
│  Render Tree Construction                           │
│  DOM + CSSOM → only visible nodes                   │
│  (display:none excluded, ::before included)         │
└─────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│  Layout (Reflow)                                    │
│  Compute exact position & size of every element     │
│  Box model, flexbox, grid calculations              │
└─────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│  Paint                                              │
│  Generate paint records (draw commands)             │
│  Per-layer painting                                 │
└─────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│  Compositing                                        │
│  Layers → GPU tiles → rasterize → composite        │
│  Final pixels on screen                             │
└─────────────────────────────────────────────────────┘


  FIRST RENDER (First Contentful Paint)

Rendering Approaches Compared

ApproachHTML SourceJS BundleTime to First PaintInteractivityCRP Impact
CSREmpty shellFull appSlow (wait for JS)After JS parse+execHeavy — entire app is CRP-blocking
SSRFull HTMLFull appFast (HTML arrives)After hydrationMedium — HTML fast, hydration costly
SSGFull HTML (pre-built)MinimalVery fast (CDN)After hydrationLight — static HTML + minimal JS
Streaming SSRChunks arrive progressivelySelectiveProgressiveProgressive hydrationOptimized — unblocks rendering early
IslandsFull HTMLPer-islandVery fastPer-island hydrationMinimal — only interactive islands load JS
Partial HydrationFull HTMLSubsetVery fastSelectiveMinimal — hydrate only what needs it

When CRP Optimization Matters Most

When Over-Optimization Becomes Harmful


2. Browser Rendering Pipeline Deep Dive

HTML Parser Internals

The HTML parser is incremental — it processes bytes as they arrive from the network. It does NOT wait for the full document.

Key behaviors:

Speculative Parsing (Preload Scanner): When the main parser is blocked by a synchronous script, a secondary parser (preload scanner) continues scanning ahead to discover resources to fetch:

This is why resources are fetched even when scripts block — the preload scanner is doing speculative work. This is one of the most impactful browser optimizations.

CSS Blocking Behavior

CSS is render-blocking, NOT parser-blocking.

DOM ready ────────────────────→ (waiting for CSSOM)
CSSOM ready ──────────────────→

                            Render Tree Construction

Implications:

JavaScript Execution & Blocking

Script TypeParser BlockingExecution TimingOrder Preserved
<script> (inline)✅ YesImmediately
<script src> (no attr)✅ YesAfter download
<script async>❌ NoASAP after download
<script defer>❌ NoAfter DOM parsed, before DOMContentLoaded
<script type="module">❌ No (deferred by default)After DOM parsed
<script type="module" async>❌ NoASAP after download

WHY synchronous scripts block: The script might call document.write() which modifies the token stream. The parser cannot continue safely without knowing if the document will be modified.

Layout Thrashing

Layout thrashing occurs when JavaScript repeatedly reads layout properties and then writes to the DOM, forcing the browser to recalculate layout synchronously multiple times in a single frame.

// BAD — forces layout recalculation on every iteration
for (let i = 0; i < elements.length; i++) {
  const height = elements[i].offsetHeight; // READ → forces layout
  elements[i].style.height = height * 2 + 'px'; // WRITE → invalidates layout
}

// GOOD — batch reads, then batch writes
const heights = elements.map((el) => el.offsetHeight); // All reads first
elements.forEach((el, i) => {
  el.style.height = heights[i] * 2 + 'px'; // All writes after
});

Properties that trigger forced synchronous layout:

Compositor Thread & GPU Acceleration

The browser has multiple threads:

┌────────────────────────────────────────────────┐
│  Main Thread                                   │
│  - JavaScript execution                        │
│  - Style calculation                           │
│  - Layout                                      │
│  - Paint (generate display list)               │
└────────────────────────────────────────────────┘
         │ (paint records)

┌────────────────────────────────────────────────┐
│  Compositor Thread                             │
│  - Layer management                            │
│  - Scroll handling (without main thread)       │
│  - CSS animations (transform, opacity)         │
│  - Commit layers to GPU                        │
└────────────────────────────────────────────────┘
         │ (tiles)

┌────────────────────────────────────────────────┐
│  Raster Threads (GPU Process)                  │
│  - Rasterize tiles into bitmaps                │
│  - GPU texture upload                          │
│  - Final compositing                           │
└────────────────────────────────────────────────┘

WHY this matters:

Frame Budget

At 60fps, each frame has 16.67ms budget:

16.67ms per frame
├── JavaScript: ~10ms max recommended
├── Style recalculation: ~1-2ms
├── Layout: ~1-2ms
├── Paint: ~1-2ms
└── Composite: ~1ms
    (+ overhead)

Long tasks (>50ms) cause jank. The browser cannot update the screen during a long task.

Browser Engine Differences

FeatureBlink (Chrome)WebKit (Safari)Gecko (Firefox)
Layer promotionAggressive (will-change)ConservativeModerate
CompositorThreaded compositorSingle-process compositorWebRender (GPU-first)
RasterizationGPU rasterization (default)CPU rasterization (mostly)GPU via WebRender
Font renderingSkiaCore TextFreeType
SchedulingRendererSchedulerRunLoopTask controller
PaintSkia → GPUCore GraphicsWebRender

Mobile differences:


3. Core Web Vitals & Metrics

Largest Contentful Paint (LCP)

AspectDetail
What it measuresTime until the largest visible content element is rendered
Good threshold≤ 2.5s
Poor threshold> 4.0s
Elements considered<img>, <video>, background-image, block-level text
Browser-level meaningRender tree painted the largest element to screen

Common causes of poor LCP:

  1. Render-blocking CSS/JS delays render tree construction
  2. Large hero images without preload
  3. Web fonts blocking text paint (FOIT)
  4. Server response time (TTFB)
  5. Client-side rendering (no HTML content until JS executes)
  6. Third-party scripts competing for bandwidth/CPU

Optimization strategies:

React implications: Hydration delays LCP when the LCP element depends on client-side state or dynamic rendering.

Cumulative Layout Shift (CLS)

AspectDetail
What it measuresTotal unexpected layout shifts during page lifetime
Good threshold≤ 0.1
Poor threshold> 0.25
Calculationimpact fraction × distance fraction
Browser-level meaningLayout recalculation moved visible elements after paint

Common causes:

  1. Images/iframes without explicit dimensions
  2. Dynamically injected content above viewport
  3. Web fonts causing text reflow (FOUT)
  4. Ads/embeds without reserved space
  5. Late-loading CSS changing layout
  6. React hydration mismatch causing re-render

Optimization:

Interaction to Next Paint (INP)

AspectDetail
What it measuresResponsiveness — time from user interaction to next visual update
Good threshold≤ 200ms
Poor threshold> 500ms
Interactionsclick, tap, keypress (NOT scroll, hover)
Browser-level meaningMain thread processing time + rendering time for response

Common causes of poor INP:

  1. Long tasks blocking main thread
  2. Heavy React re-renders on interaction
  3. Synchronous layout calculations in event handlers
  4. Large DOM trees (slow style recalculation)
  5. Excessive third-party scripts

Optimization:

Other Key Metrics

MetricWhatGoodBrowser Meaning
FCPFirst Contentful Paint≤ 1.8sFirst DOM content painted (text, image, canvas)
TTFBTime to First Byte≤ 0.8sServer processing + network latency
TBTTotal Blocking Time≤ 200msSum of time tasks exceed 50ms (between FCP and TTI)
Speed IndexVisual completeness over time≤ 3.4sHow quickly viewport is visually populated

Lab Data vs Field Data

AspectLab (Synthetic)Field (RUM)
SourceControlled environmentReal users
ToolsLighthouse, WebPageTestCrUX, Analytics RUM
ConsistencyReproducibleVariable
CoverageSingle device/networkAll devices/networks
Use caseDebugging, CI/CDReal-world impact
MetricsAll metricsCWV subset
Mobile testingSimulated throttlingActual devices

RUM Architecture:

User Browser

    ├── PerformanceObserver (LCP, CLS, INP)
    ├── Navigation Timing API
    ├── Resource Timing API
    └── Long Task API


    Beacon/fetch → Analytics Endpoint → Dashboard

4. Learning Roadmap by Skill Level

Level 1 — Newbie

Core concepts:

Common beginner mistakes:

  1. Placing <script> in <head> without defer/async
  2. Loading unused CSS for the page
  3. Using enormous unoptimized images
  4. Not specifying image dimensions
  5. Loading all JavaScript upfront
  6. Using custom fonts without fallback strategy
  7. Not understanding why pages show blank before content appears
  8. Ignoring mobile network constraints
  9. Adding too many third-party scripts
  10. Not using browser DevTools Network tab

Exercises:

  1. Open DevTools Network tab → identify render-blocking resources on any site
  2. Add defer to a script and observe DOMContentLoaded timing change
  3. Remove a CSS file and observe FOUC behavior
  4. Add width/height to all images on a page and measure CLS
  5. Use Lighthouse on a personal project and fix one red metric
  6. Compare page load with/without a large synchronous script
  7. Implement lazy loading for below-fold images
  8. Move a CSS <link> from <body> to <head> and observe rendering difference
  9. Use font-display: swap and observe text visibility during font load
  10. Throttle network to Slow 3G in DevTools and experience a typical user’s load

Level 2 — Junior

Core concepts:

Anti-patterns:

Mini project ideas:

  1. Build a landing page scoring 100 on Lighthouse Performance
  2. Implement critical CSS extraction for a multi-page site
  3. Set up route-based code splitting in a React app
  4. Create a font loading strategy with size-adjusted fallbacks
  5. Build an image component with responsive srcset and lazy loading
  6. Analyze and fix a waterfall chain in a Next.js app
  7. Implement a performance budget in CI (fail build if bundle > X KB)
  8. Build a skeleton loading UI that prevents layout shifts
  9. Compare SSR vs CSR load performance with WebPageTest
  10. Create a resource hints strategy (preload, preconnect, dns-prefetch)

Level 3 — Senior

Core concepts:

Production-grade project examples:

  1. Implement streaming SSR with selective hydration in Next.js
  2. Build a virtualized list handling 100k items with stable scroll performance
  3. Create a performance monitoring dashboard with RUM data
  4. Architect an island-based page with Astro mixing React/Svelte
  5. Build CI/CD pipeline with Lighthouse CI and performance regression alerts
  6. Implement image optimization pipeline (responsive, WebP/AVIF, CDN)
  7. Create a font subsetting and loading pipeline for multi-brand site
  8. Build a React app with zero layout shifts (CLS = 0)
  9. Implement resource prioritization strategy for an e-commerce PDP
  10. Architect a dashboard with virtualization, web workers, and compositor animations

Level 4 — Expert

Core concepts:

What experts care about that juniors miss:

Advanced discussion topics:

  1. When does promoting a layer hurt more than help?
  2. How does content-visibility: auto affect rendering cost?
  3. What’s the actual cost of React reconciliation vs DOM mutation?
  4. How does streaming SSR interact with browser progressive rendering?
  5. When should you use web workers vs main thread?
  6. How does HTTP/3 change resource loading strategy?
  7. What’s the memory cost of will-change on mobile?
  8. How does CSS containment affect layout invalidation scope?
  9. When is requestAnimationFrame not enough?
  10. How do you architect performance governance for a 50-engineer team?
  11. What’s the performance cost of CSS-in-JS at scale?
  12. How do you handle performance on low-end Android (2GB RAM)?
  13. What’s the rendering cost of shadow DOM?
  14. How does edge rendering change CRP optimization?
  15. When should you pre-render vs server-render vs client-render?

Level 5 — Browser / Rendering Engineer Mindset

Blink rendering architecture:

                   Blink

    ┌────────────────┼────────────────┐
    │                │                │
 DOM/Style       Layout Engine     Paint
 (StyleEngine)   (LayoutNG)       (Paint → DisplayList)
    │                │                │
    └────────────────┼────────────────┘

              Compositor (cc/)

              ┌──────┴──────┐
              │             │
         Tile Manager   GPU Process
              │             │
              └──────┬──────┘

                  Display

Threading model:

Scheduler internals:

Future browser rendering directions:


5. React / Next.js / Astro Rendering Performance

WHY React Apps Often Become CRP-Heavy

  1. Bundle size: React + ReactDOM ≈ 45KB gzipped — must download, parse, execute before anything interactive
  2. CSR by default: Without SSR, browser sees empty <div id="root"> — CRP produces blank screen
  3. Hydration cost: Server-rendered HTML still needs full JS to become interactive
  4. Component tree depth: Deep trees mean expensive reconciliation
  5. State-driven re-renders: Unoptimized state causes cascade re-renders
  6. CSS-in-JS runtime: Styled-components/Emotion inject styles at runtime, blocking paint

WHY Hydration Becomes Expensive

Server HTML arrives (fast FCP)


Browser must:
1. Download entire JS bundle
2. Parse JavaScript
3. Execute React
4. React walks entire tree comparing server HTML to virtual DOM
5. Attach event handlers


Page is interactive (slow TTI)

On low-end mobile: Steps 1-5 can take 3-10 seconds. During this time, the page LOOKS interactive but clicking does nothing.

React Optimization Patterns

// Streaming SSR (React 18)
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
  bootstrapScripts: ['/main.js'],
  onShellReady() {
    // Shell (layout + loading states) rendered
    response.statusCode = 200;
    pipe(response);
  },
});

// Selective hydration with Suspense
function Page() {
  return (
    <Layout>
      <Header /> {/* Hydrates immediately */}
      <Suspense fallback={<Skeleton />}>
        <HeavyContent /> {/* Hydrates when ready */}
      </Suspense>
      <Suspense fallback={<Skeleton />}>
        <Comments /> {/* Hydrates last, or on interaction */}
      </Suspense>
    </Layout>
  );
}

Next.js Rendering Strategies

StrategyUse WhenCRP Impact
Static (SSG)Content doesn’t change per-requestMinimal — served from CDN
ISRContent changes occasionallyLow — cached + background regen
Server ComponentsComponent doesn’t need interactivityLow — zero client JS
Streaming SSRDynamic content, fast TTFB neededOptimized — progressive rendering
Client ComponentNeeds browser APIs, interactivityHigher — requires hydration
// Next.js App Router — Server Component (zero client JS)
async function ProductPage({ id }: { id: string }) {
  const product = await getProduct(id); // Runs on server only
  return (
    <div>
      <h1>{product.name}</h1>
      <Image
        src={product.image}
        width={800}
        height={600}
        priority // Preloads LCP image
        fetchPriority="high"
      />
      <AddToCartButton product={product} /> {/* Client Component */}
    </div>
  );
}

Astro Islands Architecture

---
// This runs at build time / server time — zero client JS
import Header from '../components/Header.astro';
import ProductCard from '../components/ProductCard.astro';
import CartWidget from '../components/CartWidget.tsx';
---

<Header /> <!-- Static HTML, no JS -->
<ProductCard /> <!-- Static HTML, no JS -->

<!-- Only this island ships JS to the client -->
<CartWidget client:visible />
<!-- Hydrates only when scrolled into view -->

Astro hydration directives:

DirectiveWhen It HydratesUse Case
client:loadImmediately on page loadCritical interactive UI
client:idleAfter page is idle (requestIdleCallback)Non-urgent interactivity
client:visibleWhen scrolled into viewportBelow-fold components
client:mediaWhen media query matchesMobile-only components
client:onlyClient-only (no SSR)Browser-API-dependent

WHY SSR Can Still Feel Slow

  1. TTFB latency: Server computation + database queries delay first byte
  2. Large HTML payload: Full HTML document takes time to download
  3. Hydration gap: Page looks ready but isn’t interactive
  4. Blocking hydration: Entire tree must hydrate before any interaction works
  5. JavaScript parse cost: Even with HTML present, JS must be parsed/compiled

Solution: Streaming + Selective Hydration + Islands


6. Setup Guide

Performance Workflow for React/Next.js/Astro/Vite Stack

1. Measure (Lighthouse + RUM)

2. Identify bottleneck (Network? JS? Layout? Paint?)

3. Fix highest-impact issue

4. Verify fix (re-measure)

5. Automate (CI/CD budget)

6. Monitor (RUM alerts)

Lighthouse Setup

# CLI
npm install -g lighthouse
lighthouse https://example.com --output=html --output-path=./report.html

# CI with Lighthouse CI
npm install -g @lhci/cli
lhci autorun --config=lighthouserc.json
// lighthouserc.json
{
  "ci": {
    "collect": {
      "url": ["http://localhost:3000"],
      "numberOfRuns": 3
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "interactive": ["error", { "maxNumericValue": 3800 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

Chrome DevTools Performance Profiling

  1. Performance tab: Record → interact → stop → analyze flame chart
  2. Key things to look for:
    • Long tasks (red corners)
    • Layout recalculations (purple bars)
    • Paint events (green bars)
    • Idle time (gray)
  3. Network tab: Waterfall view → identify blocking chains
  4. Rendering tab: Enable “Paint flashing”, “Layout shift regions”
  5. Layers tab: Inspect compositing layers and memory

Bundle Analysis Setup (Vite)

# vite-plugin-bundle-analyzer
npm install -D rollup-plugin-visualizer

# vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
});

Next.js Optimization Setup

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920],
  },
  experimental: {
    optimizeCss: true, // Critical CSS extraction
  },
};

Image Optimization

<!-- Responsive images with modern formats -->
<picture>
  <source srcset="hero.avif" type="image/avif" />
  <source srcset="hero.webp" type="image/webp" />
  <img
    src="hero.jpg"
    alt="Hero"
    width="1200"
    height="600"
    loading="eager"
    fetchpriority="high"
    decoding="async"
  />
</picture>

Font Optimization

<!-- Preload critical font -->
<link
  rel="preload"
  href="/fonts/Inter-var.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

<style>
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/Inter-var.woff2') format('woff2');
    font-display: optional; /* Best for CLS — no layout shift */
    /* Size-adjusted fallback for zero CLS */
    size-adjust: 107%;
    ascent-override: 90%;
    descent-override: 22%;
    line-gap-override: 0%;
  }
</style>

Performance Budget in CI/CD

# GitHub Actions example
- name: Bundle size check
  run: |
    npm run build
    MAX_SIZE=200000  # 200KB
    ACTUAL=$(stat -f%z dist/assets/index-*.js 2>/dev/null || stat --printf="%s" dist/assets/index-*.js)
    if [ "$ACTUAL" -gt "$MAX_SIZE" ]; then
      echo "❌ Bundle too large: ${ACTUAL} bytes (max: ${MAX_SIZE})"
      exit 1
    fi

Real-User Monitoring Setup

// Lightweight RUM with web-vitals library
import { onLCP, onCLS, onINP } from 'web-vitals';

function sendMetric(metric: { name: string; value: number; id: string }) {
  navigator.sendBeacon('/api/metrics', JSON.stringify(metric));
}

onLCP(sendMetric);
onCLS(sendMetric);
onINP(sendMetric);

7. Performance Tooling Comparison

ToolPurposeProsConsCI/CDLearning Curve
LighthouseAudit overall performanceFree, comprehensive, actionableLab only, simulated throttlingLow
WebPageTestDeep waterfall & video analysisReal devices, multi-location, filmstripComplex UI, slowerMedium
Chrome DevToolsReal-time debuggingLive profiling, flame chart, layersManual, not automatedMedium
PageSpeed InsightsQuick CWV check (lab + field)Includes CrUX data, freeLimited detailLow
CalibreContinuous monitoringAlerting, budgets, trendsPaidMedium
SpeedCurvePerformance visualizationBeautiful dashboards, competitive analysisPaid, expensiveMedium
Bundle AnalyzerJS bundle compositionVisual treemap, find bloatBuild-time onlyLow
React ProfilerReact render performanceComponent-level timingReact-only, dev modeMedium
PerfettoLow-level browser tracingChrome internals, thread-levelVery complexHigh
Chrome TracingBrowser process tracingFull rendering pipelineExpert-levelHigh

When to Use What

ScenarioBest Tool
Quick performance checkPageSpeed Insights
Debug specific rendering issueChrome DevTools Performance tab
CI/CD performance gateLighthouse CI
Understand load waterfallWebPageTest
React re-render debuggingReact Profiler
Bundle size monitoringBundle Analyzer in CI
Browser internals debuggingPerfetto / Chrome Tracing
Continuous monitoringCalibre / SpeedCurve
Real-user impactRUM (web-vitals + analytics)

8. Cheatsheet

Script Loading Patterns

<!-- Critical inline script (rare) -->
<script>
  /* only for critical above-fold logic */
</script>

<!-- Standard deferred (most scripts) -->
<script defer src="/main.js"></script>

<!-- Async for independent scripts (analytics) -->
<script async src="/analytics.js"></script>

<!-- Module (deferred by default) -->
<script type="module" src="/app.js"></script>

<!-- Preload critical script -->
<link rel="preload" as="script" href="/critical.js" />

<!-- Prefetch for next page -->
<link rel="prefetch" as="script" href="/next-page.js" />

Critical CSS Pattern

<head>
  <!-- Inline critical CSS -->
  <style>
    /* Above-fold styles only */
    .header { ... }
    .hero { ... }
  </style>

  <!-- Defer full CSS -->
  <link
    rel="preload"
    href="/styles.css"
    as="style"
    onload="this.onload=null;this.rel='stylesheet'"
  />
  <noscript><link rel="stylesheet" href="/styles.css" /></noscript>
</head>

Image Optimization Patterns

<!-- LCP image: eager + high priority -->
<img
  src="hero.webp"
  fetchpriority="high"
  loading="eager"
  decoding="async"
  width="1200"
  height="600"
  alt=""
/>

<!-- Below-fold: lazy -->
<img
  src="card.webp"
  loading="lazy"
  decoding="async"
  width="400"
  height="300"
  alt=""
/>

<!-- Responsive -->
<img
  srcset="img-400.webp 400w, img-800.webp 800w, img-1200.webp 1200w"
  sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
  src="img-800.webp"
  alt=""
/>

Layout Thrashing Fixes

// ❌ Read-write-read-write
elements.forEach((el) => {
  el.style.width = el.offsetWidth + 10 + 'px';
});

// ✅ Batch reads, then batch writes
const widths = elements.map((el) => el.offsetWidth);
elements.forEach((el, i) => {
  el.style.width = widths[i] + 10 + 'px';
});

// ✅ Use requestAnimationFrame
requestAnimationFrame(() => {
  // All DOM writes here
});

GPU Optimization Patterns

/* ✅ Compositor-only animation (smooth, off main thread) */
.animate {
  will-change: transform;
  transform: translateX(0);
  transition: transform 0.3s ease;
}
.animate:hover {
  transform: translateX(100px);
}

/* ❌ Main-thread animation (causes layout + paint) */
.animate-bad {
  transition: left 0.3s ease;
  left: 0;
}
.animate-bad:hover {
  left: 100px;
}

/* Promote to own layer (use sparingly) */
.layer {
  will-change: transform;
  /* or: transform: translateZ(0); — hack, less ideal */
}

/* Content visibility for off-screen content */
.below-fold {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;
}

React Optimization Patterns

// ✅ Lazy load routes
const Dashboard = lazy(() => import('./Dashboard'));

// ✅ Use startTransition for non-urgent updates
function Search() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  function handleChange(e) {
    setQuery(e.target.value); // Urgent: update input
    startTransition(() => {
      setResults(search(e.target.value)); // Non-urgent: can be interrupted
    });
  }
}

// ✅ Virtualize long lists
import { useVirtualizer } from '@tanstack/react-virtual';

Performance Debugging Checklist

Mobile Optimization Checklist


9. Real-World Engineering Mindset

Landing Pages

Problem: Must render meaningful content ASAP. Every ms of delay = bounce.

StrategyBest ForTrade-off
SSG + CDNStatic contentCan’t personalize
Edge SSRPersonalized landingMore complex infra
Critical CSS inlineFast first paintLarger HTML, harder to maintain
Preload hero imageFast LCPOne extra resource hint to manage

Senior choice: SSG with edge middleware for personalization. Inline critical CSS. Preload LCP image with fetchpriority="high". Ship < 50KB JS total.

Dashboard Apps

Problem: Large component trees, frequent updates, heavy data.

StrategyApproach
RenderingCSR is fine (authenticated, no SEO needed)
BundleRoute-based code splitting, lazy load panels
ListsVirtualize tables/lists with >50 rows
UpdatesUse useDeferredValue / startTransition for filters
WorkersOffload data processing to web worker
AnimationsCompositor-only transitions for panels

E-commerce Product Pages

Problem: LCP is hero image. Revenue depends on speed. SEO matters.

Senior architecture:

  1. Server Components for product data (zero client JS)
  2. Streaming SSR for dynamic content (reviews, recommendations)
  3. <Image priority fetchPriority="high"> for hero
  4. Preconnect to image CDN
  5. Skeleton UI for below-fold (no CLS)
  6. Client islands only for Add to Cart, size selector

Heavy Animations

Problem: 60fps while animating complex UI.

Rules:

  1. Only animate transform and opacity (compositor thread)
  2. Promote animated elements to own layer (will-change: transform)
  3. Avoid animating width/height/top/left (triggers layout)
  4. Use CSS animations over JS when possible (declarative, optimizable)
  5. For complex sequences: Web Animations API or GSAP (batches DOM)
  6. Monitor frame drops in Performance tab
  7. Test on throttled CPU (4x slowdown)

Mobile constraint: Each layer = GPU texture = memory. On a phone with 3GB RAM shared between system and browser, 50+ layers can cause crashes.

Infinite Scrolling

Problem: DOM grows unbounded → layout becomes expensive → scroll janks.

Solution architecture:

Viewport (visible)
├── Render only visible items (virtualization)
├── Buffer zone above/below (prevent flicker)
├── Recycle DOM nodes (not create new ones)
├── IntersectionObserver for trigger (no scroll listener)
└── Placeholder height for scroll position

Use @tanstack/react-virtual or react-window. Keep DOM node count < 500.


10. Brainstorm / Open Questions

Rendering Pipeline (12)

  1. Why does CSS block rendering but not HTML parsing?
  2. What happens if CSSOM isn’t ready when DOM finishes parsing?
  3. How does the browser decide which elements get their own compositing layer?
  4. What’s the rendering cost difference between visibility: hidden and display: none?
  5. When does content-visibility: auto skip rendering work vs just paint?
  6. How does the render tree differ from the DOM tree?
  7. What triggers a full layout vs partial layout?
  8. Why can’t the browser just skip layout for unchanged elements?
  9. What’s the difference between paint invalidation and layout invalidation?
  10. How does the browser prioritize which layers to rasterize first?
  11. What happens to rendering when main thread is blocked by JS for 500ms?
  12. How does contain: strict actually limit rendering scope?

Browser Behavior (12)

  1. Why did browsers implement a preload scanner?
  2. How does speculative parsing differ between Chrome, Safari, and Firefox?
  3. What’s the actual cost of document.write() in modern browsers?
  4. How does the browser decide request priority for subresources?
  5. Why does Safari handle back/forward cache differently than Chrome?
  6. How does the browser’s task scheduler prioritize rendering vs input?
  7. What happens when you have 100 <link rel="preload"> tags?
  8. How does HTTP/3 change how browsers handle resource loading?
  9. Why do browsers limit concurrent connections per origin in HTTP/1.1?
  10. How does the browser decide when to show a loading indicator?
  11. What triggers a browser repaint without relayout?
  12. How do passive event listeners affect compositor performance?

Networking (12)

  1. When is TTFB more important than total download time?
  2. How does streaming HTML change the browser’s parsing behavior?
  3. What’s the performance cost of too many preconnect hints?
  4. When does server push (HTTP/2) hurt more than help?
  5. How does DNS prefetch priority compare to preconnect?
  6. What’s the cost of a redirect chain on mobile?
  7. How does CDN cache partitioning affect CRP?
  8. When should you inline resources vs use cache?
  9. How does 103 Early Hints change CRP optimization?
  10. What’s the bandwidth cost of over-preloading?
  11. How does network priority work across multiple iframes?
  12. When does a Service Worker hurt first-load performance?

Hydration (12)

  1. Why does React hydration walk the entire DOM tree?
  2. How does selective hydration in React 18 actually work?
  3. What causes hydration mismatch and what’s the rendering cost?
  4. When is progressive hydration better than islands?
  5. How does Astro’s client:visible actually implement deferred hydration?
  6. What’s the CPU cost of hydration on a budget Android phone?
  7. Can you hydrate only event handlers without full reconciliation?
  8. How does React Server Components eliminate hydration cost?
  9. What’s the memory overhead of keeping both server HTML and virtual DOM?
  10. When does hydration ordering matter for user experience?
  11. How does streaming SSR interact with hydration timing?
  12. What if the user interacts before hydration completes?

GPU/Compositing (12)

  1. When does layer promotion increase memory pressure beyond benefit?
  2. How does will-change actually work at the GPU level?
  3. What’s the memory cost per compositing layer on mobile?
  4. How does the GPU handle transparent overlapping layers?
  5. What happens when GPU memory is exhausted?
  6. How does transform: translateZ(0) differ from will-change: transform?
  7. When do CSS animations run on compositor vs main thread?
  8. What forces a CSS animation to fall back to main thread?
  9. How does the browser decide tile sizes for rasterization?
  10. What’s the rendering cost of backdrop-filter: blur()?
  11. How does isolation: isolate affect compositing?
  12. When does GPU rasterization outperform CPU rasterization?

React Architecture (12)

  1. Why do React re-renders cascade down the tree?
  2. When does React.memo hurt performance instead of helping?
  3. How does Suspense boundaries affect streaming SSR chunks?
  4. What’s the rendering cost of context changes vs prop drilling?
  5. How does React’s batching prevent layout thrashing?
  6. When should you split a Client Component into Server + Client?
  7. How does useTransition actually defer rendering work?
  8. What’s the compositing impact of React-driven animations?
  9. How does React’s synthetic event system affect INP?
  10. What’s the paint cost of conditional rendering vs CSS display toggle?
  11. How does React’s reconciler interact with requestIdleCallback?
  12. When does component code splitting help vs hurt hydration?

Mobile Performance (12)

  1. Why does the same page perform differently on iOS vs Android?
  2. How does thermal throttling affect rendering over time?
  3. What’s the impact of 120Hz displays on frame budgets?
  4. How does mobile Safari’s “click delay” affect INP?
  5. What’s the network variability on 4G that lab testing misses?
  6. How does low memory affect compositing on Android?
  7. When should you serve different JS bundles for mobile vs desktop?
  8. How does the virtual keyboard appearance affect layout?
  9. What’s the GPU difference between a flagship and budget phone?
  10. How does iOS WKWebView handle rendering differently than Safari?
  11. Why are touch handlers more costly than mouse handlers?
  12. How does viewport meta tag affect initial rendering?

CI/CD Performance (12)

  1. How do you prevent performance regression in a monorepo?
  2. What’s a meaningful performance budget for a React SPA?
  3. When does Lighthouse CI give misleading results?
  4. How do you track performance across feature branches?
  5. What’s the right p75 threshold for RUM alerting?
  6. How do you isolate third-party script impact in CI?
  7. When should performance gates block deployment?
  8. How do you test rendering performance in CI (no GPU)?
  9. What’s the cost of running WebPageTest in CI for every PR?
  10. How do you measure the performance impact of a design system change?
  11. When does bundle size not correlate with runtime performance?
  12. How do you handle performance in preview deployments?

Product Trade-offs (11)

  1. When should you prioritize fast first load vs fast interactions?
  2. How do you justify performance work to product managers?
  3. When does A/B testing infrastructure hurt performance?
  4. Should above-fold priority always override below-fold?
  5. How do you balance developer experience with performance?
  6. When is progressive enhancement impractical?
  7. How do you handle performance on feature-flag-heavy apps?
  8. When should you serve a lighter experience to slow connections?
  9. How do you measure the revenue impact of performance improvements?
  10. When is “good enough” performance acceptable?
  11. How do you handle third-party marketing scripts that tank performance?

11. Practice Questions

Beginner (30)

Q1. What does the browser need before it can render the first pixel?

Q2. True or False: Images block the Critical Rendering Path.

Q3. What’s the difference between <script defer> and <script async>?

Q4. Why does a <link rel="stylesheet"> in the <head> delay first paint?

Q5. Which loads faster — an image with loading="lazy" or loading="eager"?

Q6. What is FOUC?

Q7. What does font-display: swap do?

Q8. What is the DOM tree?

Q9. True or False: CSS is parser-blocking.

Q10. What happens when you put a <script> tag in the middle of <body> without defer/async?

Q11. What is the CSSOM?

Q12. Why should images have width and height attributes?

Q13. What is render-blocking?

Q14. True or False: <script type="module"> is deferred by default.

Q15. What does the preload scanner do?

Q16. Which image format should you prefer for photos on the web?

Q17. What’s the purpose of <link rel="preconnect">?

Q18. True or False: The browser can render a page with only the DOM tree (no CSSOM).

Q19. What does decoding="async" do on an <img> tag?

Q20. What’s the difference between DOMContentLoaded and load events?

Q21. Why is a white screen shown before content appears?

Q22. What does fetchpriority="high" do?

Q23. True or False: Inline styles are render-blocking.

Q24. What happens if CSS fails to load?

Q25. What is a long task?

Q26. What property triggers layout when read?

Q27. What is the purpose of <link rel="dns-prefetch">?

Q28. True or False: display: none elements are included in the render tree.

Q29. Why shouldn’t you lazy-load the hero image?

Q30. What is the render tree?


Junior (30)

Q31. Your page has good TTFB but poor FCP. What should you investigate?

Q32. What’s the difference between preload and prefetch?

Q33. A page has CLS of 0.35. Which of these is most likely the cause?

Q34. How does code splitting improve CRP?

Q35. What is the “hydration gap” in SSR?

Q36. Your Lighthouse score shows “Eliminate render-blocking resources.” What are likely culprits?

Q37. When should you use <link rel="preload" as="image">?

Q38. What causes a network waterfall and how do you fix it?

Q39. True or False: loading="lazy" works on all browsers for iframes.

Q40. What is critical CSS and when should you use it?

Q41. A React app has good TTFB (SSR) but poor LCP. What’s happening?

Q42. What does <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> do?

Q43. How does font-display: optional differ from swap?

Q44. What’s the performance impact of CSS @import?

Q45. How does the Coverage tab in DevTools help with CRP?

Q46. A site loads 2MB of JavaScript. What’s the mobile rendering impact?

Q47. When should you use srcset vs <picture>?

Q48. What is the Speculation Rules API?

Q49. True or False: HTTP/2 eliminates the need for bundling JavaScript.

Q50. How do you measure layout shifts in DevTools?

Q51. What is “Total Blocking Time” and how does it relate to CRP?

Q52. How does Next.js <Image> component optimize CRP?

Q53. What’s a performance budget and how should you set one?

Q54. How does <link rel="modulepreload"> differ from <link rel="preload" as="script">?

Q55. A user reports “the page loads but nothing happens when I click buttons.” What’s the diagnosis?

Q56. How does <script> placement affect perceived performance?

Q57. What causes “layout shift from web fonts”?

Q58. How do you preload a font correctly?

Q59. What’s the difference between First Paint and First Contentful Paint?

Q60. How does <link rel="preload"> differ from putting the resource in HTML?


Senior (30)

Q61. Your React app has INP of 350ms on interaction. The Performance trace shows a 200ms “Recalculate Style” event. What’s causing this?

Q62. Explain how streaming SSR interacts with the browser’s incremental rendering.

Q63. When does will-change hurt performance?

Q64. A Next.js page has LCP of 4.2s despite fast TTFB (200ms). The LCP element is a hero image. What’s your debugging approach?

Q65. How does content-visibility: auto actually reduce rendering work?

Q66. True or False: React’s useMemo reduces rendering pipeline cost.

Q67. How would you architect a performance monitoring system for a 50-person frontend team?

Q68. What’s the difference between layout containment and size containment?

Q69. A virtualized list janks when scrolling fast. What’s happening?

Q70. How does React Server Components eliminate CRP cost compared to regular SSR?

Q71. Explain the performance trade-offs of CSS-in-JS at scale.

Q72. When should you use web workers for rendering performance?

Q73. How does the Scheduler API (scheduler.postTask) improve rendering?

Q74. A page has 200 compositing layers shown in DevTools Layers panel. Is this a problem?

Q75. How do you implement zero-CLS font loading?

Q76. Explain the rendering cost of a large DOM tree (5000+ nodes).

Q77. How does HTTP/3 (QUIC) change CRP optimization strategy?

Q78. What forces a CSS animation to fall back from compositor to main thread?

Q79. How would you diagnose why React hydration takes 800ms on a product page?

Q80. True or False: requestAnimationFrame guarantees your callback runs before the next paint.

Q81. What’s the difference between paint and composite in terms of cost?

Q82. How does <link rel="preload" as="fetch"> differ from <link rel="preload" as="script">?

Q83. How does the View Transitions API improve perceived performance?

Q84. What’s the rendering cost of backdrop-filter: blur(10px) and how do you mitigate it?

Q85. How do you implement performance regression detection in CI for a monorepo?

Q86. Explain how contain: paint affects the rendering pipeline.

Q87. What’s the performance difference between CSS Grid and Flexbox for layout computation?

Q88. A user on a budget Android phone reports 3-second INP. Your analytics show p75 INP is 180ms. How do you investigate?

Q89. How does fetchpriority actually work at the browser scheduling level?

Q90. What architectural patterns prevent performance degradation as a React app scales to 200+ routes?


Expert / Browser Engineer (30)

Q91. How does Blink’s LayoutNG differ from the legacy layout engine in terms of rendering performance?

Q92. Explain how Chrome’s compositor thread handles scroll without involving the main thread.

Q93. What causes “checkerboarding” during fast scroll and how do browsers mitigate it?

Q94. How does React’s fiber architecture interact with browser rendering frames?

Q95. True or False: GPU rasterization is always faster than CPU rasterization.

Q96. How does the browser’s task scheduler decide between running JavaScript vs rendering?

Q97. Explain the performance implications of style engine’s selector matching strategy.

Q98. How does WebRender (Firefox/Gecko) differ architecturally from Chrome’s compositor?

Q99. What happens at the rendering level when you call element.style.transform = ... in a loop 1000 times synchronously?

Q100. How does the browser’s pre-paint phase work in the modern rendering pipeline?

Q101. Explain how IntersectionObserver is more efficient than scroll event + getBoundingClientRect.

Q102. What’s the memory architecture of compositing layers and how does it affect mobile?

Q103. How does scheduler.yield() differ from setTimeout(0) for breaking up long tasks?

Q104. Explain how the browser handles position: fixed elements in the compositing pipeline.

Q105. What causes “jank” during CSS transitions that should be compositor-accelerated?

Q106. How does Chrome’s renderer process architecture (site isolation) affect CRP?

Q107. What is “rendering starvation” and when does it occur?

Q108. How do passive event listeners actually improve compositor performance?

Q109. Explain the rendering implications of the content-visibility: auto with contain-intrinsic-size.

Q110. How does the browser decide the order of resource loading for a page with 50+ resources?

Q111. What’s the cost model of forced style recalculation (invalidation) in browser engines?

Q112. How does isolation: isolate create a new stacking context and what are the compositing implications?

Q113. Explain how Chrome’s speculative loading (Speculation Rules API) affects the rendering pipeline.

Q114. What’s the rendering difference between opacity: 0, visibility: hidden, and display: none?

Q115. How does the browser handle rendering for a <canvas> element with frequent updates?

Q116. What are the performance implications of CSS @layer for the style engine?

Q117. How does a browser’s GPU process handle texture memory limits?

Q118. Explain the performance characteristics of requestIdleCallback vs scheduler.postTask({priority: 'background'}).

Q119. What’s the rendering cost of a large box-shadow vs filter: drop-shadow()?

Q120. How would you use Perfetto/Chrome Tracing to diagnose a 200ms input delay?


12. Personalized Recommendations

For Your Stack (React + Next.js + Astro + Vite + TypeScript)

Priority CRP concepts:

  1. React hydration cost — this is your #1 CRP bottleneck in Next.js apps
  2. Server Components — eliminate client JS for data-heavy components
  3. Streaming SSR — unblock first paint with progressive rendering
  4. Image optimization — Next.js Image + fetchpriority for LCP
  5. Bundle architecture — Vite’s code splitting + tree shaking
  6. Island architecture — Astro for content-heavy sites
  7. Font loading — zero-CLS font strategy
  8. Core Web Vitals monitoring — RUM in production

Common Mistakes Frontend Engineers Make

  1. Using CSR when SSR/SSG would be better (React SPA habit)
  2. Not preloading LCP images
  3. Over-hydrating (sending JS for static content)
  4. Ignoring mobile performance (testing only on fast laptops)
  5. Bundle size creep without monitoring
  6. Layout thrashing in animation/scroll handlers
  7. CSS-in-JS runtime overhead at scale
  8. Not using fetchpriority on critical resources
  9. Lazy loading above-fold content
  10. Not measuring before and after optimization

60-Day Learning Plan

Week 1-2: Foundations

Week 3-4: React Performance

Week 5-6: Advanced Optimization

Week 7-8: Tooling & CI

Week 9-10: Deep Rendering

Week 11-12: Architecture

Milestone targets:


Beginner

TopicLink
Critical Rendering Pathhttps://web.dev/articles/critical-rendering-path
How Browsers Workhttps://developer.chrome.com/blog/inside-browser-part1/
Render-blocking resourceshttps://web.dev/articles/render-blocking-resources
Core Web Vitalshttps://web.dev/articles/vitals
DOM Constructionhttps://web.dev/articles/critical-rendering-path/constructing-the-object-model
Render Treehttps://web.dev/articles/critical-rendering-path/render-tree-construction
MDN: Critical Rendering Pathhttps://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path
Loading Performancehttps://web.dev/learn/performance

Intermediate

TopicLink
LCPhttps://web.dev/articles/lcp
CLShttps://web.dev/articles/cls
INPhttps://web.dev/articles/inp
Optimize LCPhttps://web.dev/articles/optimize-lcp
Resource Hintshttps://web.dev/articles/preload-critical-assets
Font Best Practiceshttps://web.dev/articles/font-best-practices
Image Optimizationhttps://web.dev/articles/choose-the-right-image-format
Lighthousehttps://developer.chrome.com/docs/lighthouse
Patterns.devhttps://www.patterns.dev

Advanced

TopicLink
Rendering Performancehttps://web.dev/articles/rendering-performance
Compositor Animationshttps://web.dev/articles/animations-guide
Layout Thrashinghttps://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing
content-visibilityhttps://web.dev/articles/content-visibility
Speculation Ruleshttps://developer.chrome.com/docs/web-platform/prerender-pages
View Transitionshttps://developer.chrome.com/docs/web-platform/view-transitions
Scheduler APIhttps://developer.chrome.com/blog/introducing-scheduler-yield
fetchpriorityhttps://web.dev/articles/fetch-priority
Performance Calendarhttps://calendar.perfplanet.com
React Server Componentshttps://react.dev/reference/rsc/server-components

Expert / Browser Internals

TopicLink
Blink Rendering Architecturehttps://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/README.md
How Blink Workshttps://docs.google.com/document/d/1aitSOucL0VHZa9Z2vbRJSyAIsAz24kX8LFByQ5xQnUg
Chromium Compositorhttps://chromium.googlesource.com/chromium/src/+/main/cc/README.md
RenderingNG Architecturehttps://developer.chrome.com/articles/renderingng-architecture
LayoutNGhttps://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/layout/ng/README.md
Perfettohttps://perfetto.dev
Chrome Tracinghttps://www.chromium.org/developers/how-tos/trace-event-profiling-tool/
Life of a Pixel (video)https://www.youtube.com/watch?v=K2QHdgAKP-s
WebRender (Firefox)https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank/

React / Next.js / Astro

TopicLink
React Hydrationhttps://react.dev/reference/react-dom/client/hydrateRoot
React Suspense (Streaming)https://react.dev/reference/react/Suspense
Next.js Performancehttps://nextjs.org/docs/app/building-your-application/optimizing
Next.js Imagehttps://nextjs.org/docs/app/api-reference/components/image
Astro Islandshttps://docs.astro.build/en/concepts/islands/
Vercel Blog (Performance)https://vercel.com/blog

14. Advanced Engineering Topics

Browser Rendering Internals — Deeper

RenderingNG (Chrome’s modern pipeline):

Content (DOM + Style)


┌─────────────────┐
│  Main Thread    │
│  - Animate      │
│  - Style        │
│  - Layout       │
│  - Pre-paint    │
│  - Paint        │
│  - Commit       │
└────────┬────────┘
         │ (commit)

┌─────────────────┐
│  Compositor     │
│  - Tiling       │
│  - Raster       │
│  - Activate     │
│  - Draw         │
│  - Submit       │
└────────┬────────┘
         │ (submit)

┌─────────────────┐
│  Viz (Display)  │
│  - Aggregate    │
│  - Display      │
└─────────────────┘

Frame Budget Engineering

For 60fps on a page with animations:

Hydration Architecture Patterns

PatternHowBest For
Full hydrationReact hydrates entire treeSmall apps
Progressive hydrationHydrate in priority orderMedium apps with clear priority
Selective hydration (React 18)Suspense boundaries hydrate independentlyLarge apps
Islands (Astro)Only interactive components hydrateContent-heavy sites
Resumability (Qwik)Serialize execution state, resume on interactionMaximum performance
Partial hydrationOnly hydrate components that need interactivityMixed static/dynamic

Performance Budgets at Scale

Per-route budget (example):
├── HTML: < 50KB (gzipped)
├── CSS: < 30KB (critical) + lazy rest
├── JS: < 100KB (initial) + lazy chunks
├── Images: < 200KB above-fold
├── Fonts: < 50KB (2 weights max)
├── LCP: < 2.5s (p75)
├── CLS: < 0.1
├── INP: < 200ms
└── TBT: < 200ms

Future Browser Rendering Directions

  1. WebGPU Compute: Offload complex rendering/data processing to GPU compute shaders
  2. Scroll-driven Animations: Declarative animations tied to scroll position, run on compositor
  3. View Transitions Level 2: Cross-document transitions without SPA
  4. CSS @scope: Style containment without specificity hacks
  5. Popover API: Native popover with compositor-optimized positioning
  6. Anchor Positioning: Layout-engine-optimized tooltip/popover positioning
  7. Navigation API: Fine-grained control over navigation for SPA performance
  8. Shared Element Transitions: Native element morphing between states

Summary

The Critical Rendering Path is the foundation of web performance. Understanding it deeply means understanding:

Next Steps

  1. Profile your most important page with Chrome DevTools Performance tab
  2. Identify your LCP element and ensure it’s preloaded with high priority
  3. Set up Lighthouse CI in your deployment pipeline
  4. Convert one data-heavy page to Server Components
  5. Implement RUM with web-vitals library

Advanced Topics to Continue


Edit page
Share this post:

Previous Post
CORS & PROXY — Quiz & Practice
Next Post
Content Security Policy (CSP)