A Core Web Vitals Guide to Resource Prioritization

Do not let the browser guess. Force it to load what matters when it matters!

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-01-22

Core Web Vitals Guide to Resource Prioritization

The browser’s default prioritization engine operates on heuristics (imperfect guesses based on file types and document location). Resources are enqueued based upon when they are discovered by either the preload scanner or the DOM parser.

That can become a problem when you consider that network bandwidth and CPU are not unlimited resources. For example: every byte transferred for a low-priority tracking script, while downloading at the same time, competes directly with the bytes needed for your Largest Contentful Paint (LCP).

Now this is not the browser's fault: in the HTML in our example the browser had no way of knowing that it had prioritized the wrong assets, which delayed critical rendering.

You are the one that knows what is important and you control this schedule through two mechanisms: Prioritization (boosting critical signals) and De-prioritization (scheduling non-critical resources until when they are less intrusive).


Browser Heuristic Limitations

Browsers assign priority based on a "computed priority" score. This score derives from the asset type (CSS, Script, Image) and its position in the HTML/DOM. While generally effective for simple documents, this system fails when resources are not recognized early on (by the preload scanner) or the wrong resources are triggered for an early download.

The Preload Scanner Limitation

To accelerate discovery, browsers employ a "preload scanner", a lightweight parser that races ahead of the main HTML parser to find resource URLs. This scanner has limitations (that make it fast and effective): It parses only HTML. It cannot see inside CSS files, it does not execute JavaScript, and it does not render (so it can't 'see if resources are visible in the viewport').
As a consequence, any resource referenced in a stylesheet (such as a background image or a web font), injected by a script, or lazy loaded, is skipped or not even seen until the main parser downloads and processes the entire webpage. This creates a "discovery delay," where the browser is effectively unaware that critical assets exist.

Resource Contention

When the browser discovers assets, it often attempts to download them simultaneously with other pending requests. If an  important LCP image competes with a medium-priority script or unimportant images (like social media icons in the footer), they split the available bandwidth. This contention extends the load time for both, pushing the LCP metric into the "Needs Improvement" zone.

Manual Prioritization Strategies

To build a fast rendering path, you must intervene manually. The goal is to maximize bandwidth for the LCP and minimize it for everything else.

1. Fix Discovery with Preloading

You must manually expose hidden assets to the preload scanner. By moving critical resources into the HTML <head> using rel="preload", you force the browser to acknowledge them immediately, eliminating the discovery delay.

The Implementation:

<!-- Expose the font to the scanner immediately -->
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter-bold.woff2" crossorigin>

<!-- Expose the LCP background image immediately -->
<link rel="preload" as="image" href="/images/hero-banner.jpg" fetchpriority="high">

2. Override LCP Heuristics

Browsers often assign "Low" or "Medium" priority to images because they do not know the final layout dimensions during the initial fetch. The browser cannot determine if an image is the LCP until after the render tree is built, which is too late.

The Implementation:

Force "High" priority status on the LCP element using fetchpriority="high". This bypasses internal heuristics and puts the image at the front of the download queue.

<!-- Force immediate high-priority fetch -->
<img src="hero.jpg" alt="Hero Product" fetchpriority="high">

3. De-prioritize Unimportant images

Freeing up bandwidth is often more effective than boosting priority. You must explicitly delay non-essential assets to clear the network pipe for critical resources.

The Implementation:

  • Below-the-fold: Use loading="lazy" to defer downloading until the user scrolls.
  • Above-the-fold Secondary: Use fetchpriority="low" for carousel slides or secondary visuals that render initially but are less important than the LCP.
  • Above the fold and visually unimportant: Bypass the preload scanner by using loading="lazy" and assign a low bandwidth. Handy for those small images like flags or icons that never catch the eye during a first render but might trigger a lot of early bandwidth requests.
<!-- LCP Image: Highest Priority -->
<img src="slide-1.jpg" fetchpriority="high">

<!-- Secondary Carousel Image: Immediate fetch, low bandwidth usage -->
<img src="slide-2.jpg" fetchpriority="low">

<!-- Translation flags: while in the viewport hide them from the preload scanner -->
<img src="dutch-flag.jpg" loading="lazy" fetchpriority="low">

<!-- Off-screen Image: Deferred fetch -->
<img src="footer-promo.jpg" loading="lazy">

4. Control Script Execution

JavaScript blocks the DOM parser. If you use standard <script> tags, the browser halts HTML parsing to download and execute the file.

The Implementation:

  • defer: Use for application logic. It downloads in parallel (low priority) and executes only after the HTML is fully parsed, preserving dependency order.
  • async: Use for independent third-party scripts (like analytics). It downloads in parallel and executes immediately upon completion, disregarding order.
  • Inject: Bypasses the preload scanner so it does not compete with early bandwidth. Injected scripts are treated as async.
  • Schedule + Inject: Inject scripts at a later time, for example when the load event has fired.
<!-- Application Logic: Non-blocking, preserves execution order -->
<script src="app.js" defer></script>

<!-- Third-party Consent: Non-blocking, independent execution -->
<script src="consent.js" async></script>

<script>
  /* Inject example analytics */
  const script = document.createElement('script');
  script.src = 'analytics.js';
  script.async = true; 
  document.head.appendChild(script);

  /* Inject + schedule example for chat */
  window.addEventListener('load', () => {
    const chatScript = document.createElement('script');
    chatScript.src = 'chat-widget.js';
    document.head.appendChild(chatScript);
}); </script>

5. Unblock CSS Rendering

CSS is render-blocking by design: the browser does not know what the page looks like without CSS. So it downloads and parses the stylesheets first.

Optimization Strategies:

  • Avoid @import: It creates sequential dependency chains that devastate performance.
  • Optimize Bundle Size: Avoid CSS files smaller than 3kB (overhead) and larger than 20kB (blocking). Ideally, target ~15kB files.
  • Async Loading: Load off-screen styles asynchronously to unblock the critical path.
  • Critical CSS Trade-off: While inlining Critical CSS improves the first pageview, it bypasses the browser cache, which can delay subsequent pageviews.

The Implementation:

Eliminate @import entirely. Use <link> tags for parallel loading. For non-critical CSS (like print styles), use the media attribute to unblock the main thread.

<!-- Critical CSS: Blocks rendering (Correct) -->
<link rel="stylesheet" href="main.css">

<!-- Print CSS: Non-blocking until print event occurs -->
<link rel="stylesheet" href="print.css" media="print">

<!-- Async Pattern: Loads with low priority, applies on load -->
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">

6. Stabilize Font Rendering

Fonts are heavy blocking resources. Effective prioritization requires strict limits on what is downloaded and control over how it renders.

Optimization Strategies:

  • Strict Preload Limits: Preload only the 1-2 most important font files (usually LCP text). Preloading 5+ fonts clogs the bandwidth.
  • Reduce Payload: Use Variable Fonts (one file for all weights) and Subsetting (remove unused characters) to minimize file size.
  • Rendering Strategy:
    • Use swap for fast rendering (avoids FOIT/invisible text).
    • Use optional to prevent CLS (avoids layout shifts on slow networks).

The Implementation:

<!-- Preload ONLY the critical subset (e.g. Header + Body) -->
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>

<style>
  @font-face {
    font-family: 'Inter Variable';
    src: url('/fonts/inter-var.woff2') format('woff2-variations');
    /* Choose based on stability requirements: */
    font-display: optional; /* No layout shift, but font might stay fallback */
    /* font-display: swap;     Fastest text visibility, but risks layout shift */
  }
</style>

Need your site lightning fast?

Join 500+ sites that now load faster and excel in Core Web Vitals.

Let's make it happen >>

  • Fast on 1 or 2 sprints.
  • 17+ years experience & over 500 fast sites
  • Get fast and stay fast!
A Core Web Vitals Guide to Resource Prioritization Core Web Vitals A Core Web Vitals Guide to Resource Prioritization