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.
- 01Predict a request's prioritySay where Chrome starts a stylesheet, a script, a picture or a fetch, and when it moves a picture up later.
- 02Explain why a file waitedRead a real load and tell three waits apart: tight mode, the line for a free connection, and a late boost.
- 03Steer priorities with fetchpriorityRaise the hero and lower what can wait, knowing that one file going sooner means others go later.
- 04Check 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 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.andThe 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:
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.
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
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
asyncordefer.
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:
- Highest
- High
- Medium
- Low
- 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 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: Highestandprint.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.jsis a plain script. The parser stops at it until it has arrived and run.analytics.jshasasync. It downloads while the parser goes on, and runs as soon as it arrives.ui.jshasdefer. It downloads while the parser goes on, and runs once the whole page has been read.
// 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: Lowandui.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.
| When the request is | Chrome starts it at |
|---|---|
| Stylesheet, found early | Highest |
| Stylesheet, found after pictures | Medium |
| Stylesheet whose media doesn't match | Lowest |
| Script, found early | High |
| Script, found after pictures | Medium |
| async or defer script | Low |
| Font, preloaded | High |
| Picture in the viewport, after layout | High |
| One of the first five large pictures | Medium |
| Any other picture | Low |
| fetch() | High |
| Prefetch | Lowest |
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.
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:
- Every picture starts at Low.
- Since Chrome 117, the first five large pictures start at Medium instead. Large means bigger than 10,000 square pixels, about 100 by 100.
- 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:
// 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:
// 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.andmint.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.
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?
// 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.jsfinished 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.
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.cssandapp.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.cssandapp.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.svgandblack.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.
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.svgfinished and a connection came free. The hero's first byte came at 466 ms, ahead ofui.js, thoughui.jswas 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 outrankedui.js. - Then came
ui.jsat 468 ms androoibos.svgat 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.
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:
highmoves the file up.lowmoves it down.autoleaves 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":
<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:
// 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:
// 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.andblack.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:
// 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:
Low → High932 ms956 msAs 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.
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:
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"));LowLowrooibos.svg: LowAny other picture: Chrome gives it Low. In the recorded shop page, rooibos.svg was Low.
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:
- 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
Sort each request by the hint it needs, if any.
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, addfetchpriority="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.
This time the hero has fetchpriority="high", so line 5 has changed. What does the last line print?
// 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}.`);
}mint.svg stayed at High. With fetchpriority="high", the hero starts at High, so layout has nothing left to boost.
This is the second recorded load, with the hero marked high. What does the second line print?
// 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.`);The other 7 waited until app.js had arrived, at 330 ms. The hero was High from the start, so tight mode let it through with the stylesheet, app.js and two large pictures.
As written, the hero was the last file to arrive. With the hero marked high, which file arrived last?
rooibos.svg. It's a Low picture of about 40 KB. Its turn came after the hero's, and a big file takes a while to arrive on a busy network.
An early stylesheet is Highest. What priority does Chrome give it with fetchpriority="low"?
High. The hint moves the stylesheet down one level from Highest, not all the way to Low. It's relative, and Chrome still loads it in the layout-blocking phase.
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?
High, the last priority. The cell shows the priority the request has now, with the one it started with underneath. Its tooltip reads "High, Initial priority: Low".
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?
fetchpriority="low". The slide drops from Medium to Low, and leaves more room for the slide on the screen.
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?
Question 1 of 7What does the second line of the scripts example print?
Read the code, then predict// 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.
Question 2 of 7Why did the hero start at Low in the recording?
Choose an answer to see the explanation.
Question 3 of 7What does the last line of the boost example print?
Read the code, then predict// 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.
Question 4 of 7What ends tight mode in Chrome?
Choose an answer to see the explanation.
Question 5 of 7In the recordings, what did fetchpriority="high" on the hero cost?
Choose an answer to see the explanation.
Question 6 of 7What does a priority change about a file's download?
Choose an answer to see the explanation.
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:
- Optimize resource loading with the Fetch Priority API, web.dev.
- The Network domain: requestWillBeSent and resourceChangedPriority, Chrome DevTools Protocol.
- resource_load_scheduler.h: tight mode, "used until the frame sees a <body> element", Chromium source.
- Network features reference: Timing breakdown phases, Queueing, Chrome for Developers.
- Fetch priority attributes, HTML Living Standard, WHATWG.
- fetchpriority, MDN.
- NetworkLogViewColumns.ts: the Network panel's columns, Chrome DevTools source.
- NetworkDataGridNode.ts: the Priority cell and its tooltip, Chrome DevTools source.
- format-utils.js: the Network Monitor's priority names, Firefox source.
- NetworkTableContentView.js: Web Inspector's Priority column, WebKit source.
- Resource.js: Web Inspector's three network priorities, WebKit source.
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.