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.
- 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:
- Ship less CSS on the first route (code-split, avoid mega-bundles of unused rules).
- Inline truly critical above-the-fold CSS only when measured and maintained.
- Load non-critical CSS with patterns that don’t block (media tricks are old; modern approach is split + coverage).
- Don’t
@importCSS 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:
- LCP candidate in initial HTML — see LCP optimization tactics.
fetchpriority="high"on the true LCP image — not on everything (priority inflation).- Never
loading="lazy"the LCP image. - Preconnect to the origin that serves the LCP/critical assets when it’s cross-origin.
- 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
- Fast document (TTFB, cache, CDN).
- HTML reveals LCP and critical CSS early.
- Defer non-critical JS; kill parser-blocking scripts in head.
- Shrink and split CSS; avoid unused framework CSS on first route.
- Font strategy that doesn’t stall forever or thrash layout.
- Preload sparingly for proven critical resources.
- Third parties off the critical path.
- 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.”
Related on this site
- Browser rendering pipeline — stages after you have DOM/CSSOM
- Core Web Vitals
- LCP optimization tactics
- How browsers load a page
- DOM, CSSOM, render tree
- HTTP caching headers
Further reading
- MDN: Critical rendering path
- web.dev: Critical rendering path
- web.dev: Render-blocking resources
- web.dev: Optimize LCP
- Priority Hints
Related guides
- BFCache Back Forward CacheHow the back/forward cache freezes pages for instant history nav, what blocks it, and how to restore state safely.
- Browser DevTools Network PanelRead waterfalls, timing phases, headers, throttling, and initiator chains in the Network panel like a production debugger.
- Browser DevTools Performance PanelRecord main-thread timelines: long tasks, style/layout/paint, frames, and how to turn flame charts into INP fixes.
- Browser Networking 101DNS, TCP/TLS, HTTP/1.1 vs H2/H3, connection reuse, and what frontend code can actually influence.
- Compositor LayersWhen browsers promote layers, why transform/opacity animate cheaply, and layer explosion costs.