Code of the Day
AdvancedBrowser Rendering

The Critical Rendering Path

The critical rendering path is the sequence of steps the browser must complete before painting the first visible frame — optimizing it is the single biggest lever on perceived load speed.

Web FoundationsAdvanced8 min read
Recommended first
By the end of this lesson you will be able to:
  • Trace the full critical rendering path from first byte to first painted frame
  • Identify which resource types block rendering and explain why
  • Distinguish First Contentful Paint from Largest Contentful Paint and what affects each
  • Apply inlining and preload to reduce the number of blocking round-trips before first paint

A user's first impression of your site is set in the first few hundred milliseconds — before they can scroll, before they can read, before they can interact. That window is determined almost entirely by the : the chain of work the browser must finish before it can paint a single visible pixel. Every millisecond you cut from this chain translates directly into a faster perceived load.

The full sequence

Start with what happens from the moment a user navigates to your page:

  1. DNS + TCP + TLS — the browser resolves the hostname, opens a TCP connection, and negotiates TLS. This is network time; the browser has not touched your HTML yet.
  2. Receive first byte of HTML — the server starts streaming the response.
  3. Parse HTML → DOM — the HTML parser builds the DOM incrementally as bytes arrive.
  4. Discover and fetch CSS — when the parser hits <link rel="stylesheet">, it dispatches a network request for the stylesheet and pauses rendering until it arrives and is parsed into the CSSOM.
  5. Parse CSS → CSSOM — the full stylesheet must be parsed before any computed styles are known.
  6. Build render tree — DOM + CSSOM are merged.
  7. Layout — geometry is calculated.
  8. Paint — pixels are filled in.
  9. First Contentful Paint (FCP) — the browser has painted the first piece of DOM content.

The bottleneck is step 4. Every stylesheet is a mandatory stop on the path — you cannot skip it.

Render-blocking resources

A prevents the browser from painting until it has been fully downloaded and processed.

CSS is always render-blocking. The browser refuses to show a styled page with only partial style information, because a rule in the final bytes of a stylesheet might completely change the layout of everything before it.

Synchronous scripts block both parsing and rendering. A <script> tag without async or defer causes the HTML parser to stop, wait for the script to download, execute it, and only then resume parsing the HTML below it.

<!-- this blocks HTML parsing at this point in the document -->
<script src="heavy-library.js"></script>

<!-- so does this, but only at the CSS-parse step -->
<link rel="stylesheet" href="large-styles.css">

This is why the old convention was: CSS in <head>, scripts at the end of <body>. The stylesheet is needed early so it can be parsed before layout; the script is deferred to after the DOM is built so it does not block parsing. Modern defer and async attributes give you finer control (covered in the next lesson).

First Contentful Paint and Largest Contentful Paint

Two browser metrics mark key moments on the critical path:

marks the moment the browser renders any DOM content — text, an image, or an SVG. FCP is the user's first signal that something is happening. It requires the critical path (DOM + all blocking CSS) to be complete.

marks when the largest visible element — typically a hero image or headline — finishes rendering. LCP is the primary Core Web Vital for perceived load speed. Google considers LCP under 2.5 seconds "good" and over 4 seconds "poor".

DNS/TCP/TLS  →  HTML bytes  →  DOM + CSSOM  →  Layout  →  Paint

                                                            FCP

              → hero image downloaded and decoded ──────────────→ LCP

A slow LCP is usually caused by one of four things: slow server response, render-blocking resources before the hero content, a large hero image with no size optimization, or the image being loaded lazily when it should not be.

Inlining critical CSS

Every stylesheet linked with <link rel="stylesheet"> adds at least one network round-trip before first paint. One technique that eliminates that round-trip is inlining the styles needed for above-the-fold content directly in a <style> block in <head>:

<head>
  <style>
    /* critical: above-the-fold styles only */
    body { margin: 0; font-family: sans-serif; }
    .hero { background: #1a1a2e; color: #fff; padding: 4rem; }
    .hero h1 { font-size: 3rem; margin: 0; }
  </style>
  <!-- load the rest asynchronously -->
  <link rel="stylesheet" href="non-critical.css" media="print"
        onload="this.media='all'">
</head>

The inline <style> is part of the HTML response itself — no extra request. The non-critical stylesheet is loaded with media="print" initially so it does not block rendering, then switched to all once it has loaded.

The trade-off: inlined CSS cannot be cached separately from the HTML. For a site with many visitors and a stable stylesheet, the cache hit rate of a linked file often outweighs the round-trip saving of inlining.

The browser discovers resources as it parses HTML from top to bottom. A large image in the <body>, a font referenced inside a stylesheet, or a JavaScript chunk loaded dynamically may not be discovered until late in the parse — too late to start downloading early enough.

<link rel="preload"> is a declaration to the browser's preload scanner: "fetch this resource at high priority right now, before the parser reaches it":

<head>
  <link rel="preload" href="hero.jpg" as="image">
  <link rel="preload" href="brand-font.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="preload" href="main-chunk.js" as="script">
</head>

The as attribute tells the browser what kind of resource it is, so it can apply the right priority and security checks. Preloading without as — or with the wrong as — results in a double download.

Only preload resources you are certain will be used on the current page. Preloading too many resources wastes bandwidth and can actually worsen LCP by competing with the resource you wanted to prioritise.

<link rel="preconnect"> is a lighter hint: it opens the DNS, TCP, and TLS connection to a third-party origin early, without downloading anything yet. This saves 100–500 ms per origin for fonts from Google Fonts, CDN scripts, or analytics endpoints.

Where to go next

You now understand what blocks the critical path and how to shorten it. The next lesson goes deeper on the individual levers you pull on <script> tags and resource hints: Loading Strategiesdefer, async, preload, prefetch, and preconnect in detail.

Finished reading? Mark it complete to track your progress.

On this page