{"id":330,"date":"2025-01-09T12:00:00","date_gmt":"2025-01-09T13:00:00","guid":{"rendered":"http:\/\/www.azxyfun.com\/?p=330"},"modified":"2025-03-19T19:11:47","modified_gmt":"2025-03-19T19:11:47","slug":"tight-mode-why-browsers-produce-different-performance-results","status":"publish","type":"post","link":"http:\/\/www.azxyfun.com\/index.php\/2025\/01\/09\/tight-mode-why-browsers-produce-different-performance-results\/","title":{"rendered":"Tight Mode: Why Browsers Produce Different Performance Results"},"content":{"rendered":"

Tight Mode: Why Browsers Produce Different Performance Results<\/title><\/p>\n<article>\n<header>\n<h1>Tight Mode: Why Browsers Produce Different Performance Results<\/h1>\n<address>Geoff Graham<\/address>\n<p> 2025-01-09T13:00:00+00:00<br \/>\n 2025-03-19T18:34:17+00:00<br \/>\n <\/header>\n<p>This article is sponsored by <b>DebugBear<\/b><\/p>\n<p>I was chatting with <a href=\"https:\/\/www.debugbear.com?utm_campaign=sm-7\">Debug<\/a><a href=\"https:\/\/www.debugbear.com\">B<\/a><a href=\"https:\/\/www.debugbear.com\">ear<\/a>\u2019s Matt Zeunert and, in the process, he casually mentioned this thing called <strong>Tight Mode<\/strong> when describing how browsers fetch and prioritize resources. I wanted to nod along like I knew what he was talking about but ultimately had to ask: <em>What the heck is \u201cTight\u201d mode?<\/em><\/p>\n<p>What I got back were two artifacts, one of them being the following video of Akamai web performance expert Robin Marx speaking at We Love Speed in France a few weeks ago:<\/p>\n<figure class=\"video-embed-container\">\n<div class=\"video-embed-container--wrapper\"><\/div>\n<\/figure>\n<p>The other artifact is a Google document originally published by <a href=\"https:\/\/blog.patrickmeenan.com\">Patrick Meenan<\/a> in 2015 but updated somewhat recently in November 2023. Patrick\u2019s blog has been inactive since 2014, so I\u2019ll simply <a href=\"https:\/\/docs.google.com\/document\/d\/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc\/edit?tab=t.0#\">drop a link to the Google document for you to review<\/a>.<\/p>\n<p>That\u2019s all I have and what I can find on the web about this thing called Tight Mode that appears to have so much influence on the way the web works. Robin acknowledged the lack of information about it in his presentation, and the amount of first-person research in his talk is noteworthy and worth calling out because it attempts to describe and illustrate how different browsers fetch different resources with different prioritization. Given the dearth of material on the topic, I decided to share what I was able to take away from Robin\u2019s research and Patrick\u2019s updated article.<\/p>\n<h2 id=\"it-s-the-first-of-two-phases\">It\u2019s The First of Two Phases<\/h2>\n<p>The fact that Patrick\u2019s original publication date falls in 2015 makes it no surprise that we\u2019re talking about something roughly 10 years old at this point. The 2023 update to the publication is already fairly old in \u201cweb years,\u201d yet Tight Mode is still nowhere when I try looking it up.<\/p>\n<p>So, how do we define Tight Mode? This is how Patrick explains it:<\/p>\n<blockquote><p>\u201cChrome loads resources in 2 phases. \u201cTight mode\u201d is the initial phase and constraints [sic] loading lower-priority resources until the body is attached to the document (essentially, after all blocking scripts in the head have been executed).\u201d<\/p>\n<p>— Patrick Meenan<\/p><\/blockquote>\n<p>OK, so we have this two-part process that Chrome uses to fetch resources from the network and the first part is focused on anything that isn\u2019t a \u201clower-priority resource.\u201d We have ways of telling browsers which resources <em>we<\/em> think are low priority in the form of the <a href=\"https:\/\/web.dev\/articles\/fetch-priority\">Fetch Priority API<\/a> and lazy-loading techniques that asynchronously load resources when they enter the viewport on scroll — all of which Robin covers in his presentation. But Tight Mode has its own way of determining what resources to load first.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/1-chrome-tight-mode.png\"><\/p>\n<p> <img loading=\"lazy\" width=\"800\" height=\"448\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/1-chrome-tight-mode.png\" alt=\"Chrome Tight Mode screenshot\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Figure 1: Chrome loads resources in two phases, the first of which is called \u201cTight Mode.\u201d (<a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/1-chrome-tight-mode.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Tight Mode discriminates resources, taking anything and everything marked as High and Medium priority. Everything else is constrained and left on the outside, looking in until the body is firmly attached to the document, signaling that blocking scripts have been executed. It\u2019s at that point that resources marked with Low priority are allowed in the door during the second phase of loading.<\/p>\n<p>There\u2019s a big caveat to that, but we\u2019ll get there. The important thing to note is that\u2026<\/p>\n<h2 id=\"chrome-and-safari-enforce-tight-mode\">Chrome And Safari Enforce Tight Mode<\/h2>\n<p>Yes, both Chrome and Safari have some working form of Tight Mode running in the background. That last image illustrates Chrome\u2019s Tight Mode. Let\u2019s look at Safari\u2019s next and compare the two.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/2-tight-mode-chrome-vs-safari.png\"><\/p>\n<p> <img loading=\"lazy\" width=\"800\" height=\"450\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/2-tight-mode-chrome-vs-safari.png\" alt=\"A screenshot comparing Tight Mode in Chrome with Tight Mode in Safari.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Figure 2: Comparing Tight Mode in Chrome with Tight Mode in Safari. Notice that Chrome allows five images marked with High priority to slip out of Tight Mode. (<a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/2-tight-mode-chrome-vs-safari.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Look at that! Safari discriminates High-priority resources in its initial fetch, just like Chrome, but we get wildly different loading behavior between the two browsers. Notice how Safari appears to exclude the first five PNG images marked with Medium priority where Chrome allows them. In other words, Safari makes all Medium- and Low-priority resources wait in line until all High-priority items are done loading, even though we\u2019re working with the exact same HTML. You might say that Safari\u2019s behavior makes the most sense, as you can see in that last image that Chrome seemingly excludes some High-priority resources out of Tight Mode. There\u2019s clearly some tomfoolery happening there that we\u2019ll get to.<\/p>\n<p>Where\u2019s Firefox in all this? It doesn\u2019t take any extra tightening measures when evaluating the priority of the resources on a page. We might consider this the \u201cclassic\u201d waterfall approach to fetching and loading resources.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/3-tight-mode-chtome-safari-firefox.png\"><\/p>\n<p> <img loading=\"lazy\" width=\"800\" height=\"447\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/3-tight-mode-chtome-safari-firefox.png\" alt=\"Comparison of Chrome, Safari, and Firefox Tight Mode\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Figure 3: Chrome and Safari have implemented Tight Mode while Firefox maintains a simple waterfall.(<a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/3-tight-mode-chtome-safari-firefox.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<h2 id=\"chrome-and-safari-trigger-tight-mode-differently\">Chrome And Safari Trigger Tight Mode Differently<\/h2>\n<p>Robin makes this clear as day in his talk. Chrome and Safari are both Tight Mode proponents, yet trigger it under differing circumstances that we can outline like this:<\/p>\n<table class=\"tablesaw break-out\">\n<thead>\n<tr>\n<th><\/th>\n<th>Chrome<\/th>\n<th>Safari<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Tight Mode triggered<\/td>\n<td>While blocking JS in the <code><head><\/code> is busy.<\/td>\n<td>While blocking JS or CSS anywhere is busy.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Notice that Chrome only looks at the document <code><head><\/code> when prioritizing resources, and <strong>only when it involves JavaScript<\/strong>. Safari, meanwhile, also looks at JavaScript, but CSS as well, and anywhere those things might be located in the document — regardless of whether it\u2019s in the <code><head><\/code> or <code><body><\/code>. That helps explain why Chrome excludes images marked as High priority in Figure 2 from its Tight Mode implementation — it only cares about JavaScript in this context.<\/p>\n<p>So, even if Chrome encounters a script file with <code>fetchpriority="high"<\/code> in the document body, the file is not considered a \u201cHigh\u201d priority and it will be loaded after the rest of the items. Safari, meanwhile, honors <code>fetchpriority<\/code> anywhere in the document. This helps explain why Chrome leaves two scripts on the table, so to speak, in Figure 2, while Safari appears to load them during Tight Mode.<\/p>\n<p>That\u2019s not to say Safari isn\u2019t doing anything weird in its process. Given the following markup:<\/p>\n<pre><code class=\"language-html\"><head>\n <!-- two high-priority scripts -->\n <script src=\"script-1.js\"><\/script>\n <script src=\"script-1.js\"><\/script>\n\n <!-- two low-priority scripts -->\n <script src=\"script-3.js\" defer><\/script>\n <script src=\"script-4.js\" defer><\/script>\n<\/head>\n<body>\n <!-- five low-priority scripts -->\n <img src=\"image-1.jpg\">\n <img src=\"image-2.jpg\">\n <img src=\"image-3.jpg\">\n <img src=\"image-4.jpg\">\n <img src=\"image-5.jpg\">\n<\/body>\n<\/code><\/pre>\n<p>\u2026you might expect that Safari would delay the two Low-priority scripts in the <code><head><\/code> until the five images in the <code><body><\/code> are downloaded. But that\u2019s not the case. Instead, Safari loads those two scripts during its version of Tight Mode.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/4-safari-deferred-scripts-head.png\"><\/p>\n<p> <img loading=\"lazy\" width=\"800\" height=\"452\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/4-safari-deferred-scripts-head.png\" alt=\"Safari deferred scripts\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Figure 4: Safari treats deferred scripts in the <code><head><\/code> with High priority. (<a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/4-safari-deferred-scripts-head.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<h2 id=\"chrome-and-safari-exceptions\">Chrome And Safari Exceptions<\/h2>\n<p>I mentioned earlier that Low-priority resources are loaded in during the second phase of loading after Tight Mode has been completed. But I also mentioned that there\u2019s a big caveat to that behavior. Let\u2019s touch on that now.<\/p>\n<p>According to Patrick\u2019s article, we know that Tight Mode is \u201cthe initial phase and constraints loading lower-priority resources until the body is attached to the document (essentially, after all blocking scripts in the head have been executed).\u201d But there\u2019s a second part to that definition that I left out:<\/p>\n<blockquote><p>\u201cIn tight mode, low-priority resources are only loaded if there are less than two in-flight requests at the time that they are discovered.\u201d<\/p><\/blockquote>\n<p>A-ha! So, there <em>is<\/em> a way for low-priority resources to load in Tight Mode. It\u2019s when there are less than two \u201cin-flight\u201d requests happening when they\u2019re detected.<\/p>\n<p>Wait, what does \u201cin-flight\u201d even mean?<\/p>\n<p>That\u2019s what\u2019s meant by less than two High- or Medium-priority items being requested. Robin demonstrates this by comparing Chrome to Safari under the same conditions, where there are only two High-priority scripts and ten regular images in the mix:<\/p>\n<pre><code class=\"language-html\"><head>\n <!-- two high-priority scripts -->\n <script src=\"script-1.js\"><\/script>\n <script src=\"script-1.js\"><\/script>\n<\/head>\n<body>\n <!-- ten low-priority images -->\n <img src=\"image-1.jpg\">\n <img src=\"image-2.jpg\">\n <img src=\"image-3.jpg\">\n <img src=\"image-4.jpg\">\n <img src=\"image-5.jpg\">\n <!-- rest of images -->\n <img src=\"image-10.jpg\">\n<\/body>\n<\/code><\/pre>\n<p>Let\u2019s look at what Safari does first because it\u2019s the most straightforward approach:<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/5-safari-tight-mode.jpg\"><\/p>\n<p> <img loading=\"lazy\" width=\"800\" height=\"231\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/5-safari-tight-mode.jpg\" alt=\"Safari Tight Mode\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/5-safari-tight-mode.jpg\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Nothing tricky about that, right? The two High-priority scripts are downloaded first and the 10 images flow in right after. Now let\u2019s look at Chrome:<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/6-chrome-tight-mode.jpg\"><\/p>\n<p> <img loading=\"lazy\" width=\"800\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/6-chrome-tight-mode.jpg\" alt=\"Chrome Tight Mode\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/tight-mode-why-browsers-produce-different-performance-results\/6-chrome-tight-mode.jpg\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>We have the two High-priority scripts loaded first, as expected. But then Chrome decides to let in the first five images with Medium priority, then excludes the last five images with Low priority. What. The. Heck.<\/p>\n<p>The reason is a noble one: Chrome wants to load the first five images because, presumably, the <a href=\"https:\/\/www.debugbear.com\/docs\/metrics\/largest-contentful-paint?utm_campaign=sm-7\">Largest Contentful Paint<\/a> (LCP) is often going to be one of those images and Chrome is hedging bets that the web will be faster overall if it automatically handles some of that logic. Again, it\u2019s a noble line of reasoning, even if it isn\u2019t going to be 100% accurate. It does muddy the waters, though, and makes understanding Tight Mode a lot harder when we see Medium- and Low-priority items treated as High-priority citizens.<\/p>\n<p>Even muddier is that Chrome appears to only accept up to two Medium-priority resources in this discriminatory process. The rest are marked with Low priority.<\/p>\n<p>That\u2019s what we mean by \u201cless than two in-flight requests.\u201d If Chrome sees that only one or two items are entering Tight Mode, then <strong>it automatically prioritizes up to the first five non-critical images<\/strong> as an LCP optimization effort.<\/p>\n<p>Truth be told, Safari does something similar, but in a different context. Instead of accepting Low-priority items when there are less than two in-flight requests, Safari accepts both Medium and Low priority in Tight Mode and from anywhere in the document regardless of whether they are located in the <code><head><\/code> or not. The exception is any asynchronous or deferred script because, as we saw earlier, those get loaded right away anyway.<\/p>\n<h2 id=\"how-to-manipulate-tight-mode\">How To Manipulate Tight Mode<\/h2>\n<p>This might make for a great follow-up article, but this is where I\u2019ll refer you directly to Robin\u2019s video because his first-person research is worth consuming directly. But here\u2019s the gist:<\/p>\n<ul>\n<li>We have these high-level features that can help influence priority, including <strong>resource hints<\/strong> (i.e., <code>preload<\/code> and <code>preconnect<\/code>), the <a href=\"https:\/\/www.debugbear.com\/blog\/fetchpriority-attribute?utm_campaign=sm-7\"><strong>Fetch Priority API<\/strong><\/a>, and <strong>lazy-loading techniques<\/strong>.<\/li>\n<li>We can indicate <code>fetchpriority="high"<\/code> and <code>fetchpriority="low"<\/code> on items.<\/li>\n<\/ul>\n<div class=\"break-out\">\n<pre><code class=\"language-html\"><img src=\"lcp-image.jpg\" fetchpriority=\"high\">\n<link rel=\"preload\" href=\"defer.js\" as=\"script\" fetchpriority=\"low\">\n<\/code><\/pre>\n<\/div>\n<ul>\n<li>Using <code>fetchpriority="high"<\/code> is one way we can get items lower in the source included in Tight Mode. Using <code>fetchpriority="low<\/code> is one way we can get items higher in the source excluded from Tight Mode.<\/li>\n<li>For Chrome, this works on images, asynchronous\/deferred scripts, and scripts located at the bottom of the <code><body><\/code>.<\/li>\n<li>For Safari, this only works on images.<\/li>\n<\/ul>\n<p>Again, watch Robin\u2019s talk for the full story <a href=\"https:\/\/youtu.be\/p0lFyPuH8Zs?feature=shared&t=1712\">starting around the 28:32 marker<\/a>.<\/p>\n<h2 id=\"that-s-tight-mode\">That\u2019s Tight\u2026 Mode<\/h2>\n<p>It\u2019s bonkers to me that there is so little information about Tight Mode floating around the web. I would expect something like this to be well-documented somewhere, certainly over at Chrome Developers or somewhere similar, but all we have is a lightweight Google Doc and a thorough presentation to paint a picture of how two of the three major browsers fetch and prioritize resources. Let me know if you have additional information that you\u2019ve either published or found — I\u2019d love to include them in the discussion.<\/p>\n<div class=\"signature\">\n <img src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" \/><br \/>\n <span>(yk)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>Tight Mode: Why Browsers Produce Different Performance Results Tight Mode: Why Browsers Produce Different Performance Results Geoff Graham 2025-01-09T13:00:00+00:00 2025-03-19T18:34:17+00:00 This article is sponsored by DebugBear I was chatting with DebugBear\u2019s Matt Zeunert and, in the process, he casually mentioned this thing called Tight Mode when describing how browsers fetch and prioritize resources. I wanted […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/posts\/330"}],"collection":[{"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/comments?post=330"}],"version-history":[{"count":1,"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/posts\/330\/revisions"}],"predecessor-version":[{"id":331,"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/posts\/330\/revisions\/331"}],"wp:attachment":[{"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/media?parent=330"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/categories?post=330"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.azxyfun.com\/index.php\/wp-json\/wp\/v2\/tags?post=330"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}