ESC

Type to search the knowledge base.

Critical Rendering Path

What blocks first paint and LCP on initial load — HTML, CSS, fonts, and scripts — and how to shorten the path without rehashing the full pipeline.

intermediate5 min read
  • browser
  • critical-rendering-path
  • performance
  • lcp

The critical rendering path (CRP) is the minimum chain of work from “user navigates” to “something useful is painted.” It is a first-load concept: discover resources, download them, parse, and get pixels on screen. It is not the full story of every frame after load.

For how DOM → style → layout → paint → composite works on every update (including scroll and animations), read the Browser rendering pipeline. This page focuses on what blocks first paint / LCP and how to restructure HTML delivery so the path is shorter.

Docs: MDN Critical rendering path, web.dev Critical rendering path, Optimize LCP.

CRP vs rendering pipeline

Critical rendering path Rendering pipeline
Question What work gates first meaningful pixels? What stages run when something changes?
Time horizon Navigation → first paint / LCP Every frame, interaction, style change
Main levers Resource order, blocking CSS/JS, discovery, TTFB Avoid layout thrash, paint volume, long tasks
Metrics FCP, LCP, TTFB INP, jank, CLS mid-session

You need both mental models. CRP optimizes startup. Pipeline optimizes ongoing smoothness. Core Web Vitals spans both: LCP is CRP-heavy; INP is pipeline + main-thread; CLS can be either.

The dependency chain (first load)

Simplified:

Navigate
  → DNS / TCP / TLS / TTFB (document)
  → Stream HTML parse
      → Discover CSS → fetch → build CSSOM (render-blocking by default)
      → Discover JS  → fetch → execute (parser-blocking without defer/async/module strategy)
      → Discover fonts / images / LCP candidates
  → DOM + CSSOM → render tree → layout → paint → composite

HTML is the discovery document. If the LCP image URL only appears after a JS bundle runs, you lengthened the critical path by an entire JS download + execute gap. That is the #1 SPA self-own for LCP.

What is “render-blocking”?

CSS

By default, stylesheets in the head block rendering — the browser avoids painting unstyled content. That is usually good (no FOUC), but huge CSS on the critical path delays first paint.

Tactics:

  1. Ship less CSS on the first route (code-split, avoid mega-bundles of unused rules).
  2. Inline truly critical above-the-fold CSS only when measured and maintained.
  3. Load non-critical CSS with patterns that don’t block (media tricks are old; modern approach is split + coverage).
  4. Don’t @import CSS chains — extra round trips.

JavaScript

Classic:

<script src="/app.js"></script> <!-- parser-blocking -->

While the script downloads and runs, HTML parsing stalls; DOM below the script isn’t built yet.

Better defaults for apps:

<script src="/app.js" defer></script>
<!-- or type="module" which defers by default -->
<script type="module" src="/app.js"></script>
Attribute Parse Order
(none) Blocks parser Immediate when hit
async Download parallel; executes when ready (may interrupt) Completion order
defer / module Download parallel; run after document parse Document order for defer

Put only what first paint needs on the critical path. Analytics, chat widgets, A/B cruft → late, deferred, or idle.

Fonts

Fonts can delay text rendering (font-display strategies). block periods hold invisible text; swap shows fallback then reflows (CLS risk). Match fallback metrics (size-adjust, etc.) and preload only the critical face.

<link
  rel="preload"
  href="/fonts/Inter-latin.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

crossorigin is required for font preloads even on same-origin — missing it wastes the preload.

Resource discovery and prioritization

Browsers prioritize using type, order, and hints:

<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<img
  src="/hero.avif"
  width="1200"
  height="630"
  alt=""
  fetchpriority="high"
/>

Rules of thumb for CRP:

  1. LCP candidate in initial HTML — see LCP optimization tactics.
  2. fetchpriority="high" on the true LCP image — not on everything (priority inflation).
  3. Never loading="lazy" the LCP image.
  4. Preconnect to the origin that serves the LCP/critical assets when it’s cross-origin.
  5. Early hints / HTTP/2 push alternatives: prefer solid HTML discovery + CDN caching over exotic tricks.

Server and TTFB

No amount of frontend CRP hygiene beats a 2s document TTFB. CDN edge caching for HTML (where personalization allows), efficient SSR, and lean middleware matter. In Next.js, understand what forces dynamic rendering — App Router overview and server components affect whether HTML is ready at the edge.

Streaming HTML can improve perceived CRP: the browser paints as chunks arrive. Don’t block the entire document on one slow API if above-the-fold can stream earlier.

Measuring the critical path

Lab:

  • Chrome Performance panel: document request → HTML → CSS → script eval → first paint markers
  • Lighthouse: render-blocking requests, LCP element, critical request chains
  • WebPageTest: waterfall + filmstrip

Field:

  • Core Web Vitals (CrUX / RUM) for LCP
  • Attribution: which URL was LCP, resource load delay vs render delay

A useful interview/debug split for LCP time:

TTFB + resource load delay + resource load duration + element render delay

CRP work attacks each term differently (server, discovery, bytes, main-thread/CSS).

Practical shortening checklist

  1. Fast document (TTFB, cache, CDN).
  2. HTML reveals LCP and critical CSS early.
  3. Defer non-critical JS; kill parser-blocking scripts in head.
  4. Shrink and split CSS; avoid unused framework CSS on first route.
  5. Font strategy that doesn’t stall forever or thrash layout.
  6. Preload sparingly for proven critical resources.
  7. Third parties off the critical path.
  8. After first paint, optimize interactions via pipeline + INP tactics — separate budget.

Interview angle

Define CRP as the path to first useful paint. Contrast with the ongoing rendering pipeline. Walk HTML discovery → render-blocking CSS → parser-blocking JS. Give one LCP discovery fix and one script defer fix. Mention TTFB.

One-liner:

“CRP is how we get first pixels: document, blocking CSS/JS, and early discovery of the LCP resource. The rendering pipeline is how every later frame is built.”

Further reading

Related guides