completefrontend
Open lab My progress
THE WEB PERFORMANCE FIELD GUIDE

How browsers prioritize requests

Every request gets a priority. See the priorities Chrome gave a real page's files, how pictures get boosted, and what fetchpriority changed for LCP.

By the end, you can
  • 01
    Predict a request's prioritySay where Chrome starts a stylesheet, a script, a picture or a fetch, and when it moves a picture up later.
  • 02
    Explain why a file waitedRead a real load and tell three waits apart: tight mode, the line for a free connection, and a late boost.
  • 03
    Steer priorities with fetchpriorityRaise the hero and lower what can wait, knowing that one file going sooner means others go later.
  • 04
    Check priorities in each browserTurn on the Priority column in DevTools, read a changed priority, and know that other browsers pick their own.

Every request gets a priority

Asha opens the Leaf and Kettle page on her phone. She wants one thing: the tea of the week, shown in a big picture of a glass of mint tea. A big picture like this, at the top of a page, is often called the hero.

The page is small, but it needs thirteen files: two stylesheets, three scripts, some menu data and seven pictures. In the last two lessons, you saw how the browser finds files early, and which ones hold back the first paint. But finding a file is only half the story. Once found, the files can't all travel at the same moment. So which one goes first?

The course loaded the page in Chrome, on a phone-sized screen. It added a 150 ms round trip to every request, and slowed the network to 5 megabits a second (5 Mbps). A round trip is a message to the server and its reply back. Here is what happened to the hero:

The hero came lastPop out in the code editor (opens in a new tab)JavaScript
// The shop page as written. Times are in ms after the visit started.const hero = { askedAt: 169, arrivedAt: 932 };const otherFiles = { count: 12, allArrivedBy: 881 }; console.log(`The browser asked for the hero at ${hero.askedAt} ms.`);console.log(`It finished arriving at ${hero.arrivedAt} ms.`);console.log(`The other ${otherFiles.count} files had all arrived by ${otherFiles.allArrivedBy} ms.`);
  • Line 1 is a comment. Every time is in milliseconds (ms) after the visit started.
  • Line 2 holds two times Chrome recorded for the hero.
  • Line 3 holds how many other files there were, and when the last of them arrived.
  • Lines 5 to 7 print The browser asked for the hero at 169 ms., It finished arriving at 932 ms. and The other 12 files had all arrived by 881 ms.

So the browser asked for the hero early. The preload scanner found it along with everything else. That's the second reader from HTML parsing and the preload scanner, which skims the HTML for file names. Yet the hero arrived last of all thirteen files. It came after the menu data. It even came after print.css, a stylesheet that is only used for printing. The picture Asha came for was the very last thing to arrive.

Why? Because every request gets a priority: a label that says how urgent it is. When the files can't all travel at once, the browser sends the most urgent ones first. The hero got a low label. Here is the whole load, one row per file, in the order each one's turn came:

The shop page as written, one row per file, in the order each one's turn came: styles.css, Highest: asked for at 168 ms, first byte at 171 ms, arrived at 329 ms; app.js, High: asked for at 168 ms, first byte at 171 ms, arrived at 332 ms; green.svg, Medium → High: asked for at 169 ms, first byte at 171 ms, arrived at 464 ms; black.svg, Medium → High: asked for at 169 ms, first byte at 171 ms, arrived at 466 ms; analytics.js, Low: asked for at 168 ms, first byte at 336 ms, arrived at 497 ms; herbal.svg, Medium: asked for at 169 ms, first byte at 336 ms, arrived at 728 ms; oolong.svg, Medium: asked for at 169 ms, first byte at 336 ms, arrived at 732 ms; white.svg, Medium: asked for at 169 ms, first byte at 336 ms, arrived at 735 ms; mint.svg, Low → High: asked for at 169 ms, first byte at 466 ms, arrived at 932 ms; ui.js, Low: asked for at 168 ms, first byte at 468 ms, arrived at 626 ms; rooibos.svg, Low: asked for at 169 ms, first byte at 498 ms, arrived at 838 ms; print.css, Lowest: asked for at 170 ms, first byte at 628 ms, arrived at 784 ms; menu.json, High: asked for at 627 ms, first byte at 729 ms, arrived at 881 ms.styles.cssHighestapp.jsHighgreen.svgMedium → Highblack.svgMedium → Highanalytics.jsLowherbal.svgMediumoolong.svgMediumwhite.svgMediummint.svgLow → Highui.jsLowrooibos.svgLowprint.cssLowestmenu.jsonHigh0 ms1,000 ms
Each row is one file. The plain part of a bar is waiting for a turn; the shaded part is arriving. The hero, mint.svg, was asked for with the first files, yet its turn came ninth.

Twelve of the files were asked for at about the same moment, so their bars all start near the left. But the hero only started arriving at 466 ms. Eight files went before it. The rest of this lesson explains why each of those eight went first.

Real-life analogyOrders in a café kitchen

A small café has one cook. At lunch, orders come in faster than she can cook them. So the waiter writes a label on each ticket: "now", "soon" or "whenever". Each time the cook is free, she takes the most urgent ticket. Two tickets with the same label are cooked in the order they came in.

In real life: The cook, who can only cook so much at once
In JavaScript: The network: a few connections and a fixed speed
In real life: A ticket for one dish
In JavaScript: A request for one file
In real life: The label on the ticket: "now", "soon" or "whenever"
In JavaScript: The priority, from Highest to Lowest
In real life: The waiter who writes the labels
In JavaScript: Chrome, which picks a priority for every request
In real life: Same label: first come, first cooked
In JavaScript: Same priority: the order the files were found
The main idea

Every request gets a priority. When the network can't carry every file at once, the priority decides which one goes next. It changes the order, not the speed. You can't set the order yourself. But you can tell the browser what matters: raise the one picture that matters most with fetchpriority="high", and lower what can wait.

How does the browser pick a label? web.dev's guide to Fetch Priority names three things it looks at:

  • The kind of file: a stylesheet, a script, a picture, or data.
  • Where the page names it: early in the head, or after the pictures.
  • For a script: whether it has async or defer.

Pictures add one more thing: whether they end up on the screen. The next two sections look at each of these in turn.

Priority levels in Chrome

Chrome has five priorities. From the top, DevTools calls them:

  1. Highest
  2. High
  3. Medium
  4. Low
  5. Lowest

Chrome's own code calls the top one VeryHigh and the bottom one VeryLow. The course read each file's priority from Chrome's own network events. This lesson writes them with the DevTools names.

Two stylesheets, two ends of the ladder

Start with the two stylesheets in the shop page's head. styles.css styles the page. print.css has media="print", so it only applies when someone prints the page.

The two stylesheets' prioritiesPop out in the code editor (opens in a new tab)JavaScript
// The two stylesheets in the head, and the priority Chrome gave each.const stylesheets = {  "styles.css": "Highest",  "print.css, media=print": "Lowest",}; for (const [file, priority] of Object.entries(stylesheets)) {  console.log(`${file}: ${priority}`);}
  • Line 1 is a comment.
  • Lines 3 and 4 hold the priority each stylesheet started with.
  • Lines 7 to 9 print one line per stylesheet: styles.css: Highest and print.css, media=print: Lowest.

Same kind of file, opposite ends. Render-blocking and parser-blocking resources showed why. The browser won't paint the page until styles.css has arrived, so every millisecond it waits is a millisecond of blank screen. print.css doesn't hold anything back on a screen. Its media doesn't match, so it can wait.

Three scripts, three different jobs

Now the three scripts in the head. They differ in how they load. The parser is the part of the browser that reads the HTML and builds the page:

  • app.js is a plain script. The parser stops at it until it has arrived and run.
  • analytics.js has async. It downloads while the parser goes on, and runs as soon as it arrives.
  • ui.js has defer. It downloads while the parser goes on, and runs once the whole page has been read.
The three scripts' prioritiesPop out in the code editor (opens in a new tab)JavaScript
// The three scripts in the head, and the priority Chrome gave each.const scripts = {  "app.js, a plain script": "High",  "analytics.js, async": "Low",  "ui.js, defer": "Low",}; for (const [file, priority] of Object.entries(scripts)) {  console.log(`${file}: ${priority}`);}
  • Lines 3 to 5 hold the priority each script started with.
  • Lines 8 to 10 print app.js, a plain script: High, analytics.js, async: Low and ui.js, defer: Low.

The plain script holds up the whole page, so it's urgent. The other two hold up nothing, so they can wait. A good way to remember it: the more a file holds back what the visitor sees, the higher it usually starts.

Data, and files found late

One more file on this page isn't in the HTML at all. Once the page has been read, ui.js asks for the menu data with fetch(). Chrome gave menu.json High. web.dev's table gives every fetch() High, unless the page asks for something else.

Where a file is named matters too. web.dev calls a stylesheet or a script early when the browser asks for it before any picture. On the shop page, all of them are early. A stylesheet or a script found after the pictures starts lower, at Medium.

Here are Chrome's starting priorities for the most common requests. Read each row as: a request of this kind starts here.

Where Chrome starts each request
When the request isChrome starts it at
Stylesheet, found earlyHighest
Stylesheet, found after picturesMedium
Stylesheet whose media doesn't matchLowest
Script, found earlyHigh
Script, found after picturesMedium
async or defer scriptLow
Font, preloadedHigh
Picture in the viewport, after layoutHigh
One of the first five large picturesMedium
Any other pictureLow
fetch()High
PrefetchLowest

A prefetch, in the last row, is a file fetched early for a page the visitor may open next.

Look at the pictures in the table. There are three rows for them, with three different priorities. The next section explains why.

Images in the viewport and late boosts

For most files, the kind of file is enough to pick a priority. For pictures, it isn't. A picture on the screen matters a lot. The same picture far down the page can wait. But when the preload scanner finds an <img> tag, the browser doesn't know yet where the picture will end up.

It only knows after layout: the step where the browser works out the size and place of every element. Then it can tell which pictures are in the viewport, the part of the page you see without scrolling.

Real-life analogyA table that's already seated

Orders reach the kitchen before the waiter knows where everyone is sitting. So each new dish starts as "whenever". Then the waiter walks around the room. When he sees a table whose guests are already seated and waiting, he moves their dish up to "now". Until his walk, that dish waited like any other.

In real life: A dish whose table nobody has checked yet
In JavaScript: A picture before layout: Low, or Medium for the first five large ones
In real life: The waiter's walk around the room
In JavaScript: Layout: working out where every element goes
In real life: Guests already sitting at their table
In JavaScript: A picture in the viewport
In real life: Moving their dish up to "now"
In JavaScript: The boost to High

Chrome handles pictures in three steps:

  1. Every picture starts at Low.
  2. Since Chrome 117, the first five large pictures start at Medium instead. Large means bigger than 10,000 square pixels, about 100 by 100.
  3. After layout, every picture in the viewport is boosted to High.

The shop page has seven large pictures. Six tea categories sit in a strip at the top. The hero sits below them. Each category picture is 240 by 180 pixels, which is 43,200 square pixels. Here is where each picture started:

Where the seven pictures startedPop out in the code editor (opens in a new tab)JavaScript
// The seven large pictures, in the order the HTML names them.const startedAtMedium = ["green.svg", "black.svg", "herbal.svg", "oolong.svg", "white.svg"];const startedAtLow = ["rooibos.svg", "mint.svg"]; console.log(`Started at Medium: ${startedAtMedium.join(", ")}.`);console.log(`Started at Low: ${startedAtLow.join(", ")}.`);
  • Line 1 is a comment.
  • Line 2 holds the first five large pictures in the HTML. Line 3 holds the sixth and the seventh.
  • Line 5 prints Started at Medium: green.svg, black.svg, herbal.svg, oolong.svg, white.svg.
  • Line 6 prints Started at Low: rooibos.svg, mint.svg.

The hero, mint.svg, is the seventh large picture in the HTML. The five Medium places were already taken, so it started at Low, like any other picture.

Then layout ran. The course's phone screen was 412 pixels wide. The strip of categories scrolls sideways, so only its first two pictures showed, the second one partly. The hero sat below the strip, on the screen too. Here are three of the pictures again, with the last priority each one had:

Which pictures were boostedPop out in the code editor (opens in a new tab)JavaScript
// Three of the pictures: the priority each started with, and the last one it had.const pictures = {  "green.svg": ["Medium", "High"],  "herbal.svg": ["Medium", "Medium"],  "mint.svg": ["Low", "High"],}; for (const [picture, [first, last]] of Object.entries(pictures)) {  console.log(first === last ? `${picture} stayed at ${first}.` : `${picture} went from ${first} to ${last}.`);}
  • Lines 3 to 5 hold the first and the last priority of three pictures.
  • Lines 8 to 10 print one line per picture. If its two priorities are the same, it stayed. If not, it went from one to the other.
  • It prints green.svg went from Medium to High., herbal.svg stayed at Medium. and mint.svg went from Low to High.

green.svg and black.svg were on the screen, so both were boosted. herbal.svg, oolong.svg and white.svg were off to the right, so they stayed at Medium. rooibos.svg stayed at Low. And the hero went from Low to High.

Two rows for the hero picture. As written: requested at Low, then layout shows it's in the viewport and Chrome boosts it to High; it arrived at 932 ms, LCP 956 ms. With fetchpriority="high": requested at High from the start; it arrived at 838 ms, LCP 856 ms.as writtenLowlayoutHigharrived at 932 msfetchpriority=highHigharrived at 838 ms
As written, the hero waited at Low until layout found it on the screen. With fetchpriority="high", it was High from the start.

So Chrome got the hero right in the end. But when? The boost comes after layout, and layout came late on this page. The hero's <img> tag comes after app.js in the HTML. app.js is a plain script, so the parser couldn't even build the hero's element until app.js had arrived, at 332 ms. Until then, the hero waited in line at Low.

Its turn came at 466 ms. To see what it waited for all that time, you need one more rule: tight mode.

Tight mode and delayed requests

Go back to the moment the browser asked for the files. At about 169 ms, it had found twelve of them, and it asked for all twelve. How many got going?

Only four got goingPop out in the code editor (opens in a new tab)JavaScript
// The shop page as written. The browser asked for 12 files at about 169 ms.const asked = 12;const gotGoing = ["styles.css", "app.js", "green.svg", "black.svg"];const appArrivedAt = 332; console.log(`${gotGoing.length} of ${asked} files got going at once: ${gotGoing.join(", ")}.`);console.log(`The other ${asked - gotGoing.length} waited until app.js had arrived, at ${appArrivedAt} ms.`);
  • Line 2 is how many files the browser asked for.
  • Line 3 lists the ones whose first bytes came back at once, at 171 ms. A file's first byte is the moment it starts arriving.
  • Line 4 is when app.js finished arriving.
  • Line 6 prints 4 of 12 files got going at once: styles.css, app.js, green.svg, black.svg.
  • Line 7 prints The other 8 waited until app.js had arrived, at 332 ms.

Why only four? Chrome held the other eight back on purpose. Chrome's source code calls this tight mode. It's used at the start of a load, while the parser is still in the page's <head>. In the code's words, it lasts "until the frame sees a <body> element". The frame here is the page.

Real-life analogySmall orders while the main course is decided

A big party sits down and starts to choose its main course. Until they decide, the kitchen takes the urgent orders and only a couple of small ones. Every other ticket waits on the rail, so the cook is free the moment the main course is ordered. Then the rail opens up.

In real life: The party still choosing its main course
In JavaScript: The parser still reading the head, before <body>
In real life: Urgent orders, cooked at once
In JavaScript: Highest and High requests, like styles.css and app.js
In real life: A couple of small orders
In JavaScript: Two requests below High: here, two large pictures
In real life: Other tickets waiting on the rail
In JavaScript: Every other request, held back
In real life: The main course is ordered
In JavaScript: The parser reaches <body>, and tight mode ends

In tight mode, two rules decide what goes:

  • Highest and High requests go at once. Here, that was styles.css and app.js.
  • Below High, only a couple go at a time. web.dev says two of the first five large pictures can load side by side. Here, that was green.svg and black.svg.

Everything else waits: the other Medium pictures, the async and defer scripts, the Low pictures and the print stylesheet. web.dev's table calls this time the layout-blocking phase. It gives the reason too: so the browser "can focus on more important resources".

On this page, tight mode lasted a long time. app.js is a plain script in the head, so the parser stopped there until it arrived, at 332 ms. Only then could it read on and reach <body>. The last lesson showed how a plain script stops the parser. Here, it kept tight mode going too.

At 171 ms: on their way, styles.css, app.js, green.svg, black.svg; waiting, analytics.js, herbal.svg, oolong.svg, white.svg, mint.svg, ui.js, rooibos.svg, print.css. At 336 ms: on their way, green.svg, black.svg, analytics.js, herbal.svg, oolong.svg, white.svg; waiting, mint.svg, ui.js, rooibos.svg, print.css.171 ms: before <body>on their waystyles.cssapp.jsgreen.svgblack.svgwaitinganalytics.jsherbal.svgoolong.svgwhite.svgmint.svgui.jsrooibos.svgprint.css336 ms: after app.json their waygreen.svgblack.svganalytics.jsherbal.svgoolong.svgwhite.svgwaitingmint.svgui.jsrooibos.svgprint.css
Two moments of the load as written. In tight mode, four files were on their way and eight waited. Once the parser reached <body>, six were on their way, one per connection. Shaded: on its way.

Then the line opens

The line opened, but not all the way. The course's test server spoke HTTP/1.1. As HTTP/1.1 and HTTP/2 showed, Chrome opens up to six connections to one server over HTTP/1.1, and each one carries one file at a time. Think of them as the cook's six pans.

Chrome's DevTools docs list the reasons a request waits in line. Two of them are "There are higher priority requests" and "There are already six TCP connections open for this origin". So the waiting files got their turns in priority order, one free connection at a time:

  • At 336 ms, four started arriving: the three Medium pictures, and analytics.js, the first Low file the page names.
  • At 464 ms, green.svg finished and a connection came free. The hero's first byte came at 466 ms, ahead of ui.js, though ui.js was found first. Chrome recorded that layout raised the hero from Low to High, and this is where it shows: by 464 ms, the hero already outranked ui.js.
  • Then came ui.js at 468 ms and rooibos.svg at 498 ms.
  • print.css, the only Lowest file, went last of the twelve, at 628 ms.

One more thing to notice. menu.json was asked for at 627 ms, at High. Its first byte came at 729 ms, right after herbal.svg finished and freed a connection. Even a High request waits when every connection is busy. A priority is a place in the line, not a pass to skip it.

Now the hero's wait makes sense. It waited twice: first in tight mode, held back at Low, and then in line for a free connection.

On HTTP/2 and HTTP/3, every file shares one connection, so there's no line for connections. web.dev explains that Chrome then also passes each request's priority on to the server. Not every server follows it. But tight mode happens inside the browser either way.

fetchpriority

Chrome boosted the hero in the end. But it had to wait for layout to find out that the hero mattered. You don't have to wait: you already know which picture matters most. So you can tell the browser.

Real-life analogyA regular says "this one's urgent"

A regular knows the café well. When she orders, she tells the waiter: "This one's urgent, my bus leaves in ten minutes." The waiter writes "now" on her ticket, and it's cooked sooner. But the cook is no faster, so someone else's dish comes a little later. And if she said it about every dish, the word would mean nothing.

In real life: The regular, who knows what matters
In JavaScript: You, the page's author
In real life: "This one's urgent"
In JavaScript: fetchpriority="high"
In real life: "No rush on this one"
In JavaScript: fetchpriority="low"
In real life: Someone else's dish coming later
In JavaScript: Other files arriving later
In real life: Saying it about every dish
In JavaScript: Raising every file, which raises none

The fetchpriority attribute is your way to say it. You can add it to <img>, <link> and <script> tags. It takes one of three values:

  • high moves the file up.
  • low moves it down.
  • auto leaves it to the browser. That's the same as leaving the attribute out.

The HTML standard says each value works "relative to other resources with the same destination". In plain words: compared with other files of the same kind. So high on a picture means "more urgent than the other pictures".

The hero, marked high

The course loaded the shop page a second time, with one change. The hero's tag got fetchpriority="high":

The hero's tag, with the hintHTML
<img id="hero" src="mint.svg" alt="A glass of mint tea" width="360" height="270" fetchpriority="high">

It's the same tag as before. The only new part is fetchpriority="high" at the end. Here is what it changed:

What the hint did for the heroPop out in the code editor (opens in a new tab)JavaScript
// The hero in the two recorded loads, in ms.const asWritten = { firstByte: 466, arrived: 932, lcp: 956 };const withHigh = { firstByte: 173, arrived: 838, lcp: 856 }; console.log(`The hero's first byte came at ${withHigh.firstByte} ms instead of ${asWritten.firstByte} ms.`);console.log(`It finished arriving ${asWritten.arrived - withHigh.arrived} ms sooner.`);console.log(`LCP went from ${asWritten.lcp} ms to ${withHigh.lcp} ms.`);
  • Line 2 holds three times from the first load. Line 3 holds the same three from the second.
  • Line 5 prints The hero's first byte came at 173 ms instead of 466 ms.
  • Line 6 prints It finished arriving 94 ms sooner.
  • Line 7 prints LCP went from 956 ms to 856 ms.

The hero was High from the start, so tight mode let it straight through. Its turn came 293 ms sooner. LCP, Largest Contentful Paint, is the moment the biggest thing on the screen is painted. Here, that's the hero. LCP came 100 ms sooner.

But wait. Its turn came 293 ms sooner, so why did it finish only 94 ms sooner? Because now it arrived alongside the other pictures from the start, sharing the same 5 Mbps. A priority decides when a file's turn comes. It doesn't make the network any faster.

On a bigger page, the gain can be bigger. web.dev reports a test of Google Flights where fetchpriority took LCP from 2.6 seconds to 1.9 seconds.

What the boost cost

Remember the regular: when her dish comes sooner, someone else's comes later. Here is what the hint cost two other pictures:

The other pictures paid for itPop out in the code editor (opens in a new tab)JavaScript
// When the first two category pictures finished arriving, in ms.const asWritten = { "green.svg": 464, "black.svg": 466 };const withHigh = { "green.svg": 571, "black.svg": 583 }; for (const picture of Object.keys(asWritten)) {  console.log(`${picture} arrived ${withHigh[picture] - asWritten[picture]} ms later.`);}
  • Line 2 holds when the first two category pictures finished arriving in the first load. Line 3 holds the same in the second.
  • Lines 5 to 7 subtract one from the other. They print green.svg arrived 107 ms later. and black.svg arrived 117 ms later.

The network was no faster, so the time the hero gained, other files lost. Here it was a good deal. The hero is the LCP element, and both category pictures still arrived before it. So raise the one picture that matters most. If you raised all seven, none of them would move ahead of the others.

Up a little, down a little

fetchpriority moves a request up or down from where Chrome would put it. It doesn't set an exact level. An early stylesheet is already Highest, so high can't raise it any further. web.dev also calls the attribute "a hint, not a directive". The browser can still decide otherwise.

fetchpriority="low" works the other way. It moves a file down, so it competes less with the files that matter. MDN adds a warning: use the attribute "sparingly", because the wrong priorities can make a page slower.

Requests made with fetch() take the same hint, as an option called priority:

A fetch that can waitJavaScript
// The reviews sit far down the page, so they can wait.
fetch("reviews.json", { priority: "low" });

Line 1 is a comment. Line 2 asks for the reviews with priority: "low". Without it, the request would start at High, like the menu data did.

Now try both loads yourself. Switch between them, and watch the hero's bar and the category pictures' bars:

Playground: two real loads
The requests, by arrival0 to 1 s
As writtenrecorded
Hero's priorityLow → High
Hero arrived932 ms
LCP956 ms
Try it yourself
The hero

As written, the hero started at Low. Six large pictures came before it in the HTML, and only the first five start at Medium. Layout boosted it to High later. It arrived at 932 ms, and LCP came at 956 ms.

Two real loads of the shop page in Chrome, with a 150 ms round trip added to every request and a 5 Mbps network. Each bar is one file, from when the browser asked for it to when it arrived. The rows are in the order the files arrived. The darker the bar, the higher the file's last priority. The priorities come from Chrome's own network events.

Seeing priorities in DevTools

You can see every request's priority in Chrome's DevTools. Open the Network panel. Right-click the header of its table, and turn on Priority. Each request now shows one of the five names from earlier. It's like reading the label on every ticket in the kitchen.

When a priority changed, DevTools keeps both: the first and the last. web.dev says you can see both with the Big request rows setting, or in the cell's tooltip.

The wait for a turn shows up too. Click a request and open its Timing tab. Time a request spent waiting in line is called Queueing there.

Before you open DevTools on your own pages, practise predicting. Pick a kind of request and a hint:

Playground: predict the priority
The lookup, shortenedJavaScript
const table = {  "async or defer script": { auto: "Low", high: "High", low: "Low" },  "One of the first five large pictures": { auto: "Medium", high: "High", low: "Low" },  "Picture in the viewport, after layout": { auto: "High", high: "High", low: "Low" },}; function priority(kind, fetchpriority = "auto") {  return table[kind][fetchpriority];}console.log(priority("async or defer script", "high"));
Any other pictureNo hint
Chrome's priorityLow
Without a hintLow
Recordedrooibos.svg: Low
Try it yourself
Chrome's priority

Any other picture: Chrome gives it Low. In the recorded shop page, rooibos.svg was Low.

Chrome's priorities, from the table in web.dev's Fetch Priority guide. Other browsers decide their own. When the shop page had a file of this kind, its recorded priority is shown too.

In the panel's code, lines 7 to 9 look a request up by its kind and its hint. Line 10 prints the answer.

Other browsers

Everything so far is Chrome. Other browsers pick priorities their own way. MDN puts it plainly: both a browser's own priorities and the effect of fetchpriority "are entirely browser dependent." Even the labels differ:

  • Firefox's Network Monitor can show a priority for each request too. It uses five names: Highest, High, Normal, Low and Lowest.
  • Safari's Web Inspector has a Priority column, hidden at first. It has only three levels: Low, Medium and High.

It's like a café down the road that writes its own words on its tickets. So check your hero in each browser you care about.

Raise it, lower it, or leave it?

Here are eight requests from different pages. For each one, ask: does the browser already get this right? If not, should it go up or down? Sort them:

Raise it, lower it, or leave it?
  • The hero picture, the page's LCP element
  • A preload for an LCP background picture
  • Carousel slides 2 to 5, hidden at first
  • A non-critical script that would otherwise get High
  • A chat widget's script, loaded with async
  • The page's main stylesheet
  • Every picture on the page
  • A prefetch for the next page
Try it yourself
0 of 8 correct

Sort each request by the hint it needs, if any.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "fetchpriority="high" is free." No. It changes the order, so other files wait longer. In the recordings, two category pictures arrived over 100 ms later.
  • "A preload makes a picture high priority." No. A preload, a <link rel="preload"> tag, gets a file found early. But a preloaded picture keeps a picture's usual priority. For the LCP picture, add fetchpriority="high" to the preload.
  • "Every browser uses Chrome's priorities." No. MDN says they're entirely browser dependent. Safari's Web Inspector doesn't even use the same levels.
  • "A picture on the screen is High from the start." No. Chrome only knows it's on the screen after layout. Until then, it's Low, or Medium if it's one of the first five large pictures.
  • "Make everything high priority." No. Priorities only make sense next to each other. Raising everything raises nothing.
  • "fetchpriority makes the download faster." No. It changes when the file's turn comes, not how fast its bytes travel. The hero's turn came 293 ms sooner, but it finished only 94 ms sooner.

Practice exercises

Answer first, then use the hints.

Exercise 1 · Warm-upA hero marked high

This time the hero has fetchpriority="high", so line 5 has changed. What does the last line print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
// Three of the pictures: the priority each started with, and the last one it had.
const pictures = {
  "green.svg": ["Medium", "High"],
  "herbal.svg": ["Medium", "Medium"],
  "mint.svg": ["High", "High"],
};

for (const [picture, [first, last]] of Object.entries(pictures)) {
  console.log(first === last ? `${picture} stayed at ${first}.` : `${picture} went from ${first} to ${last}.`);
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFive got going

    This is the second recorded load, with the hero marked high. What does the second line print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    // The page with fetchpriority="high" on the hero. Again, 12 files asked for at about 170 ms.
    const asked = 12;
    const gotGoing = ["styles.css", "app.js", "green.svg", "black.svg", "mint.svg"];
    const appArrivedAt = 330;
    
    console.log(`${gotGoing.length} of ${asked} files got going at once: ${gotGoing.join(", ")}.`);
    console.log(`The other ${asked - gotGoing.length} waited until app.js had arrived, at ${appArrivedAt} ms.`);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeThe last file to arrive

      As written, the hero was the last file to arrive. With the hero marked high, which file arrived last?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeLowering an early stylesheet

        An early stylesheet is Highest. What priority does Chrome give it with fetchpriority="low"?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeTwo priorities in one cell

          In DevTools, with Big request rows on, a picture's Priority cell shows two words, one above the other: Low and High. Which word is on top?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeA picture that can wait

            A carousel's second slide is hidden at first, but it gets Medium and competes with the first slide. What attribute do you add to it?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask three things. What kind of file is it? Where does the page name it? Did anything change its priority later?

              The priorities quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does the second line of the scripts example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // The three scripts in the head, and the priority Chrome gave each.
                const scripts = {
                  "app.js, a plain script": "High",
                  "analytics.js, async": "Low",
                  "ui.js, defer": "Low",
                };
                
                for (const [file, priority] of Object.entries(scripts)) {
                  console.log(`${file}: ${priority}`);
                }

                Choose an answer to see the explanation.

              2. Question 2 of 7Why did the hero start at Low in the recording?

                Choose an answer to see the explanation.

              3. Question 3 of 7What does the last line of the boost example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // Three of the pictures: the priority each started with, and the last one it had.
                const pictures = {
                  "green.svg": ["Medium", "High"],
                  "herbal.svg": ["Medium", "Medium"],
                  "mint.svg": ["Low", "High"],
                };
                
                for (const [picture, [first, last]] of Object.entries(pictures)) {
                  console.log(first === last ? `${picture} stayed at ${first}.` : `${picture} went from ${first} to ${last}.`);
                }

                Choose an answer to see the explanation.

              4. Question 4 of 7What ends tight mode in Chrome?

                Choose an answer to see the explanation.

              5. Question 5 of 7In the recordings, what did fetchpriority="high" on the hero cost?

                Choose an answer to see the explanation.

              6. Question 6 of 7What does a priority change about a file's download?

                Choose an answer to see the explanation.

              7. Question 7 of 7Do Firefox and Safari give requests the same priorities as Chrome?

                Choose an answer to see the explanation.

              Key takeaways

              • Chrome gives every request one of five priorities, from Highest to Lowest.
              • When the files can't all travel at once, the priority decides which one goes next. It changes the order, not the speed.
              • Files that hold back the first paint start high. async and defer scripts, print stylesheets and most pictures start low.
              • Pictures start at Low, or Medium for the first five large ones. After layout, the ones in the viewport are boosted to High.
              • In tight mode, until the parser reaches <body>, only Highest and High requests and a couple of others go.
              • fetchpriority="high" on the hero lets it start at High. It helps, and other files pay for it.
              • Priorities are browser dependent. Check them in each browser's own tools.

              Remember the one-liner.
              Priorities set the order, not the speed: raise the one picture that matters, and lower what can wait.

              Sources

              The facts on this page come from these standards, docs and browser source code:

              Coming next: The critical rendering path. Here, the files arrived in priority order. Next: what the browser does with them, from the DOM and the CSS to layout and the first paint.

              CompleteFrontend Clear concepts. Working examples.