Learn Frontend Part 9: Browser Rendering and Web Performance - Critical Rendering Path and Core Web Vitals

Learn Frontend Part 9: Browser Rendering and Web Performance - CodingHumans
Learn Frontend - Part 9 of 15 | Full roadmap← DOM and Events | React Fundamentals →

A fast site is not just nice to have: slow pages lose users, rank lower in search, and fail Core Web Vitals. To make pages fast you need to know how the browser turns code into pixels, what blocks that process, and which optimisations actually move the numbers. Performance questions are a favourite in senior frontend interviews for exactly this reason.

This is Part 9 of the Learn Frontend series. We cover the critical rendering path, reflow and repaint, smooth animations, Core Web Vitals, and practical loading and runtime optimisations.

1. The critical rendering path

  1. Parse HTML into the DOM. A normal <script> pauses parsing until it downloads and runs (use defer or async, Part 2).
  2. Parse CSS into the CSSOM. CSS is render-blocking: nothing is painted until the CSS for the page is ready.
  3. Render tree: combine DOM and CSSOM, keeping only visible elements (not display: none ones).
  4. Layout (reflow): calculate the size and position of every box.
  5. Paint: fill in pixels for text, colours, borders, shadows and images, in layers.
  6. Composite: the GPU combines the layers in the right order onto the screen.

To show something useful sooner: keep critical CSS small (inline it for above-the-fold content), defer non-critical JavaScript, preload the hero image and key fonts, and avoid long chains of requests that each wait for the previous one.

2. Reflow, repaint and layout thrashing

  • Reflow (layout) happens when geometry changes: width, height, margins, font size, adding or removing elements. It can cascade to many elements and is the most expensive step.
  • Repaint happens when only appearance changes (colour, background, visibility) without changing layout.
  • Composite-only changes, namely transform and opacity on an element in its own layer, skip layout and paint entirely. That is why animations should use them.

Layout thrashing happens when code alternates between writing styles and reading layout properties (offsetHeight, getBoundingClientRect()), forcing the browser to recalculate layout again and again in a single frame.

// Bad: read, write, read, write... forces a layout on every iteration
items.forEach(el => {
  const h = el.offsetHeight;          // READ (forces layout)
  el.style.height = h * 2 + 'px';     // WRITE (invalidates layout)
});

// Good: batch all reads, then all writes
const heights = items.map(el => el.offsetHeight);      // all READS
items.forEach((el, i) => {
  el.style.height = heights[i] * 2 + 'px';              // all WRITES
});

// Animate with transform instead of top/left
card.style.transform = 'translateX(200px)';             // composite only

3. Core Web Vitals

MetricMeasuresGoodMain fixes
LCP (Largest Contentful Paint)When the main content appears2.5 s or lessFast server and CDN, preload hero image, avoid render-blocking resources, SSR
INP (Interaction to Next Paint)How quickly the page responds to input200 ms or lessBreak up long tasks, less JavaScript, avoid heavy work in event handlers
CLS (Cumulative Layout Shift)How much content jumps around0.1 or lessSet image and embed sizes, reserve space for ads and banners, use font-display wisely

These come from real-user (field) data at the 75th percentile, so a perfect Lighthouse score on a fast laptop does not guarantee good field results. Our detailed guide covers each metric for React and Next.js apps.

4. Loading performance: ship less, ship smarter

  • Code splitting: load JavaScript for a route or feature only when needed, using dynamic import() or React.lazy.
  • Compression and caching: serve Brotli or gzip, use long-lived caching for hashed files, and a CDN (Part 1).
  • Images: modern formats (WebP, AVIF), responsive srcset, correct dimensions, lazy loading below the fold, and fetchpriority="high" for the LCP image.
  • Fonts: self-host or preconnect, subset them, preload the key file and use font-display: swap or optional.
  • Third-party scripts: analytics, chat widgets and tag managers are frequent culprits; load them late or with async.
  • Bundle analysis: find and remove large or duplicated dependencies; prefer tree-shakeable libraries (Part 15).
// Load a heavy module only when the user needs it
button.addEventListener('click', async () => {
  const { openEditor } = await import('./editor.js');   // separate chunk
  openEditor();
});

// React: split a route or heavy component
const Chart = React.lazy(() => import('./Chart'));
// <Suspense fallback={<Spinner />}><Chart data={data} /></Suspense>

5. Runtime performance

  • Long tasks: any task over 50 ms blocks input. Split work into chunks, yield to the browser between them, or move heavy computation to a Web Worker.
  • Large lists: rendering thousands of rows is slow; virtualisation (windowing) renders only the visible rows.
  • Animations: animate transform and opacity, use requestAnimationFrame, and use will-change sparingly.
  • Memory leaks: remove event listeners, clear timers and observers, and abort requests when components unmount (Part 11).
  • Frameworks: in React, avoid unnecessary re-renders and keep expensive work out of render; our React rendering article explains how.

6. Measuring before optimising

  • Lighthouse (DevTools) gives lab scores and specific suggestions.
  • The Performance panel records a timeline of tasks, layout, paint and long tasks; look for red-flagged long tasks and forced reflows.
  • The Network panel with throttling shows how the page loads on slow connections and devices.
  • Real user monitoring (the web-vitals library, Search Console, analytics) shows what actual visitors experience.
Interview tip: when asked "how would you make this page faster?", start by asking how it was measured, separate loading problems from interaction problems, then name specific fixes for each Core Web Vital.
Key takeaways
  • The browser builds DOM and CSSOM, then the render tree, then layout, paint and composite; CSS and synchronous scripts block rendering.
  • Reflow is expensive, repaint less so, and transform/opacity changes can skip both.
  • Avoid layout thrashing by batching DOM reads before writes.
  • Core Web Vitals: LCP for loading, INP for responsiveness, CLS for visual stability, measured on real users.
  • Ship less JavaScript with code splitting, optimise images and fonts, break up long tasks, and always measure first.

Practice problems

Use Chrome DevTools on your own project or any website to practise these.

  1. Run Lighthouse on a site, note LCP, CLS and Total Blocking Time, and list the top three opportunities.
  2. Record a Performance profile while scrolling and find long tasks and forced reflows.
  3. Create a layout-thrashing loop, measure it, then fix it by batching reads and writes.
  4. Animate an element using left and then transform; compare frame rates in the Performance panel.
  5. Add width and height to images on a page and measure the change in CLS.
  6. Code-split a large component with dynamic import and compare bundle sizes.
  7. Render 10,000 rows, then switch to a virtualised list and compare.

Interview questions and FAQ

What is the critical rendering path?

The sequence of steps the browser takes to turn HTML, CSS and JavaScript into pixels: building the DOM and CSSOM, combining them into a render tree, then layout, paint and compositing. Optimising it means showing useful content as early as possible.

What is the difference between reflow and repaint?

Reflow recalculates element positions and sizes after geometry changes and can affect many elements. Repaint redraws pixels when only appearance changes. Reflow is more expensive and usually triggers a repaint too.

Why should I animate transform and opacity?

Changes to transform and opacity can be handled by the compositor on the GPU without recalculating layout or repainting, so animations stay smooth at 60 frames per second.

What are Core Web Vitals?

Google's user-centric performance metrics: Largest Contentful Paint (loading), Interaction to Next Paint (responsiveness) and Cumulative Layout Shift (visual stability). They are measured on real users and affect search ranking.

What is code splitting?

Breaking a JavaScript bundle into smaller chunks loaded on demand, typically per route or feature, using dynamic import(). Users then download only the code they need for the current page.

Learn Frontend - Part 9 of 15 | Full roadmap← DOM and Events | React Fundamentals →

Comments

Answered 0/0Correct 0