Html: Proposal: a non-render-blocking way to load stylesheets

Created on 8 May 2018  Â·  12Comments  Â·  Source: whatwg/html

Proposal: let’s have something like

<link rel="stylesheet" href="..." async />

for loading and applying stylesheets without blocking the page rendering.

User story
I, as an application developer who cares about performance, want to have a non-render-blocking way to load non-critical CSS.

Critical CSS is the part of site styles that are required for the very first rendering. This could be e.g. CSS for above-the-fold content. Non-critical CSS is the remaining styles.

Specific use cases

  • In a news site: load and apply styles for the page layout and the news content + hide the comments/ads blocks. Then, without blocking the initial rendering, load and apply styles for comments and ads.

  • On a landing page: load and apply styles for above-the-fold content + hide the content below the fold. Then, without blocking the initial rendering, load and apply styles for below-the-fold stuff.

additioproposal style

Most helpful comment

Existing solutions

A. Element added with JavaScript

Something like:

document.addEventListener('DOMContentLoaded', () => {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '...';
  document.head.appendChild(link);
})

Pros:

  • Works

Cons:

  • It’s a hack, so it’s hard to promote this as a proper solution
  • Doesn’t allow the browser to start prefetching early
  • Works differently in different engines (AFAIK would still block rendering in Blink if the page wasn’t rendered at the moment of adding)

B. Non-matching media param + JS

Something like this:

<link rel="stylesheet" href="..." media="only fake-media" />
<script>
  setTimeout(() => {
    document.querySelector('link').media = 'screen';
  }, 0);
</script>

Pros:

  • Works
  • Allows the browser to start prefetching early

Cons:

  • Still a hack (which makes it hard to promote this as a proper solution for performance)

C. <link rel="preload" onload>

Something like this:

<link rel="preload" href="..." as="style" onload="this.rel = 'stylesheet'" />

Pros:

  • Works
  • Allows the browser to start prefetching early
  • Easy to add

Cons:

  • Might work slower than loading stylesheets with <link rel="stylesheets" /> (browsers might give <link rel="preload"> and <link rel="stylesheet"> different priorities)
  • Not that easy to add in React (and probably other frameworks)
  • A terrible hack

Proposed solution

A. <link async>

Something like this:

<link rel="stylesheet" href="..." async />

Pros:

  • Works
  • Allows the browser to start prefetching early
  • Easy to enable (and not a hack)
  • More or less matches the <script async /> declaration, so is easy to remember
  • Gracefully degrades to plain <link> in older browsers

Cons:

  • Adds the async attribute which won’t make sense for non-stylesheet <link> tags. (Through there’s already the as attribute which works only with <link rel="preload" />, and that’s fine.)

B. <link rel="stylesheet-async">

Something like this:

<link rel="stylesheet-async" href="..." />

Pros:

  • Works
  • Allows the browser to start prefetching early
  • Easy to enable (and not a hack)
  • Doesn’t add the new async attribute which only makes sense for some rels

Cons:

  • Adds the new rel (seems like a huge addition)
  • Backwards-incompatible
  • Less nice than the async attribute (because it doesn’t match <script async>)

All 12 comments

Existing solutions

A. Element added with JavaScript

Something like:

document.addEventListener('DOMContentLoaded', () => {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '...';
  document.head.appendChild(link);
})

Pros:

  • Works

Cons:

  • It’s a hack, so it’s hard to promote this as a proper solution
  • Doesn’t allow the browser to start prefetching early
  • Works differently in different engines (AFAIK would still block rendering in Blink if the page wasn’t rendered at the moment of adding)

B. Non-matching media param + JS

Something like this:

<link rel="stylesheet" href="..." media="only fake-media" />
<script>
  setTimeout(() => {
    document.querySelector('link').media = 'screen';
  }, 0);
</script>

Pros:

  • Works
  • Allows the browser to start prefetching early

Cons:

  • Still a hack (which makes it hard to promote this as a proper solution for performance)

C. <link rel="preload" onload>

Something like this:

<link rel="preload" href="..." as="style" onload="this.rel = 'stylesheet'" />

Pros:

  • Works
  • Allows the browser to start prefetching early
  • Easy to add

Cons:

  • Might work slower than loading stylesheets with <link rel="stylesheets" /> (browsers might give <link rel="preload"> and <link rel="stylesheet"> different priorities)
  • Not that easy to add in React (and probably other frameworks)
  • A terrible hack

Proposed solution

A. <link async>

Something like this:

<link rel="stylesheet" href="..." async />

Pros:

  • Works
  • Allows the browser to start prefetching early
  • Easy to enable (and not a hack)
  • More or less matches the <script async /> declaration, so is easy to remember
  • Gracefully degrades to plain <link> in older browsers

Cons:

  • Adds the async attribute which won’t make sense for non-stylesheet <link> tags. (Through there’s already the as attribute which works only with <link rel="preload" />, and that’s fine.)

B. <link rel="stylesheet-async">

Something like this:

<link rel="stylesheet-async" href="..." />

Pros:

  • Works
  • Allows the browser to start prefetching early
  • Easy to enable (and not a hack)
  • Doesn’t add the new async attribute which only makes sense for some rels

Cons:

  • Adds the new rel (seems like a huge addition)
  • Backwards-incompatible
  • Less nice than the async attribute (because it doesn’t match <script async>)

I’m personally in favor of solution A.

Also, to explain the “it’s a hack” cons. These hacks might look OK for folks with web background (including myself), but, in my experience, they’re a part of “death by thousand cuts” for non-web developers. That’s why I prioritize this. It’s not cool to have conversations like “How do I do this _[basic stuff]_?” – “Oh, there’s no proper solution for this, use one of these hacks depending on your requirements” – “Uh, okay.”

cc @jakearchibald @igrigorik @yoavweiss

You may add link tag with ref preload to the top and link tag with ref stylesheet to the bottom, without async attribute.
It should work same way.

I am confused by this, this already exists years ago https://www.filamentgroup.com/lab/async-css.html

@montogeek main idea — don't use js and use only one styles definition. Actually idea is quite good.

Also http2 push kinda solve this problem too. Almost.

One another question: rel="stylesheet-async" is not backward compatible, so is will take it's time before anyone may really use it.

It should work same way.

Just tested with Chrome stable, and this didn’t work. Do you have a reproducible example?

Also http2 push kinda solve this problem too. Almost.

Server push would solve the preloading issue – but AFAIK the browser won’t apply the pushed stylesheet without the <link> tag. So that’s not a complete solution unfortunately :/

One another question: rel="stylesheet-async" is not backward compatible, so is will take it's time before anyone may really use it.

Great point, I missed this! <link rel="stylesheet" async /> should be backwards-compatible though. I’ll add that into pros/cons.

Right now:

…content…
<link rel="stylesheet" href="…"><script> </script>

"content" can render before the stylesheet loads in every browser except Chrome (ticket).

This means you can achieve async stylesheet loading using a preload plus a stylesheet at the bottom of the document. You can also incrementally load styles by placing stylesheets just before the first element that needs them.

The above is more versatile, and it'd be nice if it was standardised (along with a change to Firefox so the <script> </script> isn't needed.

An async attribute on <link> seems sensible, but I don't think it's as useful as incrementally-applying CSS.

In general I agree with @jakearchibald in that this is a decent idea but might not be most useful to incrementally apply styles. I'm wondering if a lot of the questions that might be brought up by this thread regarding the async attribute are related to https://github.com/whatwg/html/issues/1349.

"content" can render before the stylesheet loads in every browser except Chrome (ticket).

FWIW, under existing solutions, (B) doesn't always work; for example, see https://github.com/whatwg/html/issues/2886, which Firefox has just about solved but it is not in stable yet. Regarding (C), I believe @wanderview told me that Firefox's preload implementation is currently turned off, so I don't think that'll load. And regarding the priority associated with preloads, the HTML Standard mentions that the priority should be the same as the as resource type, but I'm not sure how many browsers actually follow this.

Thanks for the feedback! Yup, rendering the page until a <link> is found should be more useful.

Closing this in favor of #1349 as it covers standardization of this <link> behavior :–)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NE-SmallTown picture NE-SmallTown  Â·  3Comments

mustaqahmed picture mustaqahmed  Â·  4Comments

NE-SmallTown picture NE-SmallTown  Â·  4Comments

samuelgoto picture samuelgoto  Â·  3Comments

petamoriken picture petamoriken  Â·  3Comments