惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
O
OpenAI News
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
云风的 BLOG
云风的 BLOG
罗磊的独立博客
S
SegmentFault 最新的问题
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
雷峰网
雷峰网
V
Visual Studio Blog
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Y
Y Combinator Blog
博客园 - 【当耐特】
G
Google Developers Blog
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
Martin Fowler
Martin Fowler
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
AI
AI
Google Online Security Blog
Google Online Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - Franky
Blog — PlanetScale
Blog — PlanetScale
Webroot Blog
Webroot Blog
PCI Perspectives
PCI Perspectives
爱范儿
爱范儿
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

CSS Wizardry

Front-End’s Missing Metric: The TBT Window Meet Your Users Where They Are with Obs.js Better Browser Caching with No-Vary-Search font-family Doesn’t Fall Back the Way You Think What Is CSS Containment and How Can I Use It? When All You Can Do Is All or Nothing, Do Nothing Obs.js: Context-Aware Web Performance for Everyone Low- and Mid-Tier Mobile for the Real World (2025) The Fastest Site in the Tour de France Making Sense of the Performance Extensibility API Why Do We Have a Cache-Control Request Header? HTML Is Not a Programming Language… Build for the Web, Build on the Web, Build with the Web Licensing Code on CSS Wizardry A Layered Approach to Speculation Rules Designing (and Evolving) a New Web Performance Score Core Web Vitals Colours The Ultimate Contract Templates for Tech Consultants: Protect Your Business and Get Paid Optimising for High Latency Environments Cache Grab: How Much Are You Leaving on the Table? Correctly Configure (Pre) Connections The Three Cs: 🤝 Concatenate, 🗜️ Compress, 🗳️ Cache What Is the Maximum max-age? How to Clear Cache and Cookies on a Customer’s Device The Ultimate Low-Quality Image Placeholder Technique Core Web Vitals for Search Engine Optimisation: What Do We Need to Know? The HTTP/1-liness of HTTP/2 In Defence of DOM­Content­Loaded Site-Speed Topography Remapped Why Not document.write()? Speeding Up Async Snippets Critical CSS? Not So Fast! Measure What You Impact, Not What You Influence Optimising Largest Contentful Paint Measuring Web Performance in Mobile Safari Site-Speed Topography Speed Up Google Fonts Real-World Effectiveness of Brotli Performance Budgets, Pragmatically Lazy Pre-Browsing with Prefetch Making Cloud.typography Fast(er) Time to First Byte: What It Is and How to Improve It Self-Host Your Static Assets Tips for Technical Interviews Cache-Control for Civilians Bandwidth or Latency: When to Optimise for Which ITCSS × Skillshare What If? CSS and Network Performance The Three Types of Performance Testing Getting to Know a Legacy Codebase Image Inconsistencies: How and When Browsers Download Images Identifying, Auditing, and Discussing Third Parties My Digital Music Setup Measuring the Hard-to-Measure Finding Dead CSS The Fallacies of Distributed Computing (Applied to Front-End Performance) Ten Years Old Relative Requirements Airplanes and Ashtrays Performance and Resilience: Stress-Testing Third Parties Refactoring Tunnels Little Things I Like to Do with Git Writing Tidy Code Configuring Git and Vim Base64 Encoding & Performance, Part 2: Gathering Data Base64 Encoding & Performance, Part 1: What’s Up with Base64? Code Smells in CSS Revisited Typography for Developers Moving CSS Wizardry onto HTTPS and HTTP/2 Ack for CSS Developers A New Year, a New Focus Preparing Vim for Apple’s Touch Bar Choosing the Correct Average CSS Shorthand Syntax Considered an Anti-Pattern CSS Wizardry Newsletter Nesting Your BEM? Improving Perceived Performance with Multiple Background Images Continue Normalising Your CSS Pure CSS Content Filter Pragmatic, Practical, and Progressive Theming with Custom Properties Refactoring CSS: The Three I’s Speaker’s Checklist: Before and After Your Talk Improving Your CSS with Parker The Importance of !important: Forcing Immutability in CSS Mixins Better for Performance Managing Typography on Large Apps White October Events Workshop Partnership BEMIT: Taking the BEM Naming Convention a Step Further Travelling Like You Want to, When You Have To Contextual Styling: UI Components, Nesting, and Implementation Detail Subtleties with Self-Chained Classes Cyclomatic Complexity: Logic in CSS Immutable CSS Can CSS Be Too Modular? More Transparent UI Code with Namespaces When to use @extend; when to use a mixin The Specificity Graph CSS Wizardry Ltd.: Year 1 in review CSS Guidelines 2.0.0
blocking=render: Why would you do that?!
Harry Roberts · 2024-08-14 · via CSS Wizardry

Written by on CSS Wizardry.

Table of Contents

Independent writing is brought to you via my wonderful Supporters.

  1. What is blocking=render?
  2. Blocking Status
    1. Blocking Files
    2. async, defer, and type=module
  3. Blocking Web Fonts
  4. A/B Testing and Experimentation
  5. tl;dr

WebKit have recently announced their intent to implement the blocking=render attribute for <script> and <style> elements, bringing them in line with support already available in Blink and generally positive sentiment in Firefox.

The blocking=render attribute allows developers to explicitly mark a resource as render blocking, but… why on earth would you want to do that?!

The short answer is: generally, you wouldn’t. Unless you know you need this behaviour, you don’t need it.

But how do you know if you do need it? Read on…

What is blocking=render?

The spec says:

A blocking attribute explicitly indicates that certain operations should be blocked on the fetching of an external resource. The operations that can be blocked are represented by possible blocking tokens, which are strings listed by the following table […]
2.5.8 Blocking attributes

Currently, there is only one token specified: render. The spec is extensible so that other values could be added as the need arises—potential scenarios that have been discussed include parse, load, and even a negation to encourage the opposite, such as blocking=!render.

Blocking Status

Generally speaking, when loading resources into web pages, there are three possible blocking states:

  1. Non-blocking: From a performance perspective, this is the most desirable. The resource is fetched and processed asynchronously while the browser is free to work on whatever other tasks there may be. The two key tasks that are not blocked are rendering and parsing.
  2. Render blocking: The next-best option for the performance conscious is render blocking. Files that are render blocking prohibit the browser from presenting the page, but do permit the browser to at least construct it.
  3. Parser blocking: The worst case scenario is a file that prevents the browser from even building the page. All parsing and rendering is blocked while the resource is fetched. Files that are parser blocking are inherently also render blocking—the browser can’t present a page that it can’t even construct.

Visually, this is how that process looks for each scenario:

Comparison of non-blocking, render-blocking, and parser-blocking resources in web performance. A visual breakdown of how different loading strategies affect rendering, parsing, and blocking behaviour in the browser.
A non-, render-, and parser-blocking file in an HTML document. Imagine the downloading file (pink) is in the <head>—even though you can never see <head> tags or their children, they still get rendered just like any other HTML, they’re just set to display: none;. That said, these diagrams also apply to a downloading file (pink) that is in the middle of the <body>. HTML is parsed line-by-line and is very predictable. We ❤️ HTML.

Blocking Files

The two main file types that impact the blocked status of a web page are stylesheets and scripts. In their default states:

  • <link rel=stylesheet href=app.css>: This will block the rendering of subsequent content, but not its parsing. The browser is free to continue parsing the HTML and building out the DOM, but cannot display any of it until app.css is fully fetched and parsed. Stylesheets are render blocking.
  • <script src=app.js></script> This will block parsing (and therefore also rendering) of subsequent content. The browser may not parse or construct any DOM until app.js is fully fetched and parsed, at which point it now has two tasks ahead of it: build the DOM and render it. Scripts are parser blocking.

All other file types are, by default, non-blocking.

The pedant in me wants to point out that even inline <script> and <style> are still technically parser blocking. Colloquially, we refer to them as non-blocking, but even for the handful of milliseconds that the browser is parsing either the JS or CSS contained in them, it’s blocked from doing anything else.

async, defer, and type=module

Without going into too much detail, the presence of any of these attributes on a <script> will cause it to fall into the first camp: non-blocking. Therefore, <script>s can occupy either extreme: non-blocking, the fastest option; or parser blocking, the slowest option.

The primary use-case for blocking=render is to grant <script>s access to the middle option: render- but not parser-blocking.

Let’s look at two examples of putting blocking=render to use.

Blocking Web Fonts

I wrote this entire section before Ryan Townsend pointed out that blocking specifically for rel=preload was removed from the spec. I’m keeping the following for posterity, but this does not currently work in any implementation.

This is one of the least compelling examples, in my opinion. Also, for this to work, the blocking attribute needs specifying for <link> elements, which is currently only possible in Blink. But let’s take a look anyway…

Imagine you’ve built a simple countdown or stopwatch app:

A digital stopwatch running an HTML file, displaying ‘00:51’ in large green text before unceremoniously changing its typeface and continuing to count down.
The change from fallback font to web font causes a very noticeable change in UI. This might be unacceptable.

Given a UI such as this, even with the best will in the world, the switch from any fallback font to the intended web font is quite a leap. Is it too much? If you decide it is, you could block on the preload of that font (if you were preloading it in the first place). That would look like this:

<link rel=preload
      as=font
      href=font.woff2
      crossorigin
      blocking=render>

Typically, I would strongly recommend not blocking rendering on web fonts. Using the relevant font-display to ensure that text can render as soon as possible is almost always the correct thing to do: reading something in the ‘wrong’ font is better than reading nothing at all.

However, in scenarios where a flash of fallback font (FOFT) might be particularly jarring—or create severe layout shifts—then perhaps waiting on the web font might (might) be the right thing to do. Maybe. I’m not actively recommending it.

Note that almost the exact same behaviour could be achieved by adding font-display: block; to the relevant @font-face rule, but blocking=render provides would have provided two distinct additions:

  1. font-display: block; will time out after three seconds, whereas blocking=render has no such timeout. In that sense, it’s much more aggressive.
  2. font-display: block; will still render the current UI, only without text—a flash of invisible text (FOIT). blocking=render won’t render anything at all.

If a web font is your content (which, for 99.999% of you, it isn’t), you might want to maybe use blocking=render. But even then, I wouldn’t.

Interestingly, Chrome exhibits blocking=render-style behaviour on web-font preloads already. It’s non-standard behaviour, but Chrome will make font preloads block rendering until they finish or until a timeout. This is already happening and you don’t need blocking=render.1

A/B Testing and Experimentation

blocking=render’s application in client-side A/B testing is, for me, its most compelling use-case.

Client-side A/B testing tools work by altering the DOM and presenting a variant of a component to a user. In order for this to work, the original DOM must already be constructed (you can’t alter a DOM if you don’t have one), so there’s an aspect of doing the work twice. A problem arises if and when a user actually sees that work happening twice. It’s a jarring experience to see one version of a hero change to something completely different in front of your eyes, and it may even influence the outcome of the experiment itself.

To circumvent this, many A/B testing tools implement what is known as an anti-flicker snippet. They deliberately hide the page (visually) until the variants have been constructed, or a timeout is met—whichever happens sooner.

This is the anti-flicker snippet from the now defunct Google Optimize.

<!-- Anti-Flicker Snippet -->
<style>
  .async-hide { opacity: 0 !important }
</style>

<script>
  (function(a,s,y,n,c,h,i,d,e) {
    s.className+=' '+y;
    h.start=1*new Date;
    h.end=i=function(){
      s.className=s.className.replace(RegExp(' ?'+y),'')
    };
    (a[n]=a[n]||[]).hide=h;
    setTimeout(function(){i();h.end=null},c);
    h.timeout=c;
  });
  (window, document.documentElement, 'async-hide', 'dataLayer', 4000, {'GTM-XXXXXX':true});
</script>

This snippet works by applying the class async-hide to the <html> element (document.documentElement). This aggressively sets opacity: 0; so that the page is rendered, only invisibly. The class is then removed either when the A/B tool’s work is done, or a 4000ms timeout is reached—whichever is first.

One immediate failing with this is that an invisible page is still interactive, and users could still click on or interact with elements inadvertently. The page is rendered, but invisibly. blocking=render ensures that the page is not rendered at all, and therefore can’t be interacted with.

Another problem is that we’re going through more paint cycles than we need to: paint the page invisibly, modify it, paint it again visibly… It would be nicer to hold off painting anything at all until we have all of the relevant information about what to paint. blocking=render gives us this ability.

A further issue is the big-reveal phenomenon: with an anti-flicker snippet, the page is totally invisible until it’s totally visible. Behind the opacity: 0;, there may well have been a progressive render of the page—which is a familiar and good user experience—but a user didn’t benefit from it. Anti-flicker snippets eschew this behaviour and take an all-or-nothing approach: nothing, nothing, nothing, everything.

Progressive rendering sequence of a webpage displaying a web performance consultancy service. The timeline from 0.8s to 1.7s shows how content and images load incrementally, highlighting the impact of rendering delays.
A regular, progressive render (top) versus an anti-flicker big-reveal (bottom). Which do you think is the better experience?

blocking=render leaves the browser to its usual rendering process, so we can still get a progressive render of the page, only now we do it in a way more akin to loading a CSS file.

Finally, and this is counter to my own preferences and beliefs as a performance engineer, we still risk leaking the experiment to the user when using an anti-flicker snippet. Knowingly hiding a page for up to four seconds feels like insanity to me, but at least we do have a timeout. The problem with anti-flicker snippets is that if that four-second timeout is reached, we’ll still display the page even if experiments haven’t completed—the 4000ms is a magic number that we use to hopefully win a race condition.

By using blocking=render, that timeout now becomes governed by the browser’s own heuristics, which is almost definitely going to be longer than four seconds. While that does terrify me, it does guarantee we don’t paint anything too soon. No more race conditions, but a potentially longer render-blocked period.

As I said at the top of the article, most of us won’t need blocking=render, and those of us who do will know that we do.

tl;dr

One handy takeaway is that, at present, blocking=render would cause any of the following:

  • <script src async></script>
  • <script src defer></script>
  • <script src type=module></script>
  • <script type=module>...</script>

…to behave like this:

  • <link rel=stylesheet href>


Frequently Asked Questions

Why would be need a blocking=render attribute?

Generally, we don’t! The primary use case thus far is to grant scripts the ability to be render- but not parser-blocking, which is not currently possible. This would be particularly helpful for A/B testing libraries who currently rely on anti-flicker snippets to achieve the results they want.