148

I'm trying to eliminate 2 CSS files that are render blocking on my site - they appear on Google Page Speed Insights. I have followed different methods, none of which were a success. But, recently, I found a post about Thinking Async and when I applied this code: <script async src="https://third-party.com/resource.js"></script>it did eliminate the problem.

However, after publishing, the page lost the styling. I'm not too sure as to what is going on because the code works but it's the styling after upload that doesn't work. Would appreciate your help with this. Thanks

Paulina994
  • 1,581
  • 2
  • 10
  • 6
  • 2
    Are you applying async to styles or scripts? The style loads some time after you load the page or it never appears? – kamus Sep 24 '15 at 13:17
  • I applied the async attribute to styles and placed them in the header. – Paulina994 Sep 24 '15 at 13:33
  • 2
    The thing with styles is that a re-rendering will be triggered if you load them late (e.g. in the body), and is not allowed in the standards (but since browsers are very forgiving it will work anyway). If the problem is slow response times from a third party server, perhaps you can simply host them on your server instead? – Josef Engelfrost Sep 24 '15 at 14:56

11 Answers11

201

2020 Update


The simple answer (full browser support):

<link rel="stylesheet" href="style.css" media="print" onload="this.media='all'">

The documented answer (with optional preloading and script-disabled fallback):

 <!-- Optional, if we want the stylesheet to get preloaded. Note that this line causes stylesheet to get downloaded, but not applied to the page. Use strategically — while preloading will push this resource up the priority list, it may cause more important resources to be pushed down the priority list. This may not be the desired effect for non-critical CSS, depending on other resources your app needs. -->
 <link rel="preload" href="style.css" as="style">

 <!-- Media type (print) doesn't match the current environment, so browser decides it's not that important and loads the stylesheet asynchronously (without delaying page rendering). On load, we change media type so that the stylesheet gets applied to screens. -->
 <link rel="stylesheet" href="style.css" media="print" onload="this.media='all'">

 <!-- Fallback that only gets inserted when JavaScript is disabled, in which case we can't load CSS asynchronously. -->
 <noscript><link rel="stylesheet" href="style.css"></noscript>

Preloading and async combined:

If you need preloaded and async CSS, this solution simply combines two lines from the documented answer above, making it slightly cleaner. And again, as detailed in the documented answer above, preloading may not actually be beneficial.

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

Additional considerations:

Note that, in general, if you're going to load CSS asynchronously, it's generally recommended that you inline critical CSS, since CSS is a render-blocking resource for a reason.

Credit to filament group for their many async CSS solutions.

This approach may not work with content security policy enabled.

Flimm
  • 136,138
  • 45
  • 251
  • 267
jabacchetta
  • 45,013
  • 9
  • 63
  • 75
  • 3
    There's no need to use loadCSS, the polyfill is enough: https://github.com/filamentgroup/loadCSS/blob/master/src/cssrelpreload.js – nathan Aug 12 '18 at 16:48
  • 1
    Preload's spec status is finally [W3C Candidate Recommendation].(https://www.w3.org/TR/preload/#targetText=Refers%20to%20a%20resource%20that,fetch%20properties%20of%20the%20link.) Firefox supports it, but still disabled by default; click on answer's "widely supported" link for "Can I Use's" description of the Firefox flag that controls it. – ToolmakerSteve Oct 16 '19 at 11:55
  • 1
    Answer updated to provide solution for full browser support, along with additional explanation. – jabacchetta Jan 09 '20 at 12:15
  • 1
    @jabacchetta the 2020 update is very much appreciated, thank you. I was looking _specifically_ for instructions written in recent times where browser support is generally better. A lot changes over 3 years on the web! – brandito Mar 13 '20 at 04:10
  • 4
    Apparently, it is better to `onload="this.rel='stylesheet'; this.onload = null"`. It is necessary to set `this.onload` to `null` to avoid this being called twice in some browsers, apparently. – Flimm Oct 21 '20 at 10:19
  • 1
    Doesn't play well for content security policy unfortunately. – DGibbs May 19 '21 at 10:40
  • I looked at the code for "loadCSS.js", and it creates an artificial event "loadcssdefined", which it realizes via polling. Polling is generally considered an undesirable solution to problems, and is the antithesis of actual events. I will not be using "loadCSS.js". – IAM_AL_X Jun 14 '21 at 20:09
  • `` does work in Firefox now – Flimm Sep 10 '22 at 10:59
  • Keep in mind that users with JS disabled will get the main CSS applied when printing, which may not be what you had in mind. This is why I prefer to use preload/prefetch and dynamically insert a link tag for main CSS instead of updating `print` to `all` via JavaScript. – thdoan Nov 22 '22 at 09:34
153

The trick to triggering an asynchronous stylesheet download is to use a <link> element and set an invalid value for the media attribute (I'm using media="none", but any value will do). When a media query evaluates to false, the browser will still download the stylesheet, but it won't wait for the content to be available before rendering the page.

<link rel="stylesheet" href="css.css" media="none">

Once the stylesheet has finished downloading the media attribute must be set to a valid value so the style rules will be applied to the document. The onload event is used to switch the media property to all:

<link rel="stylesheet" href="css.css" media="none" onload="if(media!='all')media='all'">

This method of loading CSS will deliver useable content to visitors much quicker than the standard approach. Critical CSS can still be served with the usual blocking approach (or you can inline it for ultimate performance) and non-critical styles can be progressively downloaded and applied later in the parsing / rendering process.

This technique uses JavaScript, but you can cater for non-JavaScript browsers by wrapping the equivalent blocking <link> elements in a <noscript> element:

<link rel="stylesheet" href="css.css" media="none" onload="if(media!='all')media='all'"><noscript><link rel="stylesheet" href="css.css"></noscript>

You can see the operation in www.itcha.edu.sv

enter image description here

Source in http://keithclark.co.uk/

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Vladimir Salguero
  • 5,609
  • 3
  • 42
  • 47
  • Ok.. I didn't know that you can reference element attributes in the on-handlers just by mentioning their name. I always thought 'event' is the only exception. – Teemoh May 23 '17 at 04:21
  • 3
    So how do you keep your page from flashing as it rerenders everything? Plus do you inline core app shell styles so your page has some initial layout instead of rendering something Lynx would be proud of? – Chris Love Jan 10 '18 at 18:38
  • 2
    Seems to still work for me. After doing this I know longer get a warning in PageSpeed Insights for this file. – AndyWarren Mar 16 '18 at 19:13
  • 3
    this is working for me (08-july -2018) . And it giving good score in pagespeed insight – Abilash Erikson Jul 08 '18 at 07:28
  • This works in staging/uat environments but not working in production environment. In production the css does not get applied at all even though the file is loaded. Any idea what could be causing this? I have tried it in multiple browsers – Sahil Feb 25 '19 at 08:46
  • 2
    IMHO, [jabachetta's newer answer using "preload"](https://stackoverflow.com/a/46750893/199364), is likely the preferred solution now. If anyone has a reason to think *this* answer is still preferable, please add a comment explaining why. Ideally linking to a resource that confirms why/when this approach still might be preferable. [Assuming you use the polyfill to support `preload` on Firefox and on older browsers - see link in first comment of that answer]. – ToolmakerSteve Oct 16 '19 at 12:18
  • Why is the condition in `if (this.media != 'all') this.media = 'all'` necessary? – Flimm Oct 21 '20 at 10:24
  • @Flimm the statement you quoted is basically the entire solution -- if you look at the `link` element, it's `media` is set to `none`, effectively disabling it -- the stylesheet will not apply to any kind of media, although the browser will download the resource. When the resource is thus downloaded, the `load` event will fire, effectively executing the code specified as by the `onload` attribute -- which will switch applicable media value of the stylesheet to apply to all media, effectively enabling it. I can't say it will work though, just explaining the supposed intention. – Armen Michaeli Nov 12 '20 at 19:01
  • @amn I understand all of that. The part I don't understand is `if (this.media != 'all') this.media = 'all'`. Why not eliminate the `if` condition, like this: `this.media = 'all'` ? – Flimm Nov 16 '20 at 08:24
  • Ah, I see -- good point. Now that you brought it up, I wonder about that myself. I haven't tested the solution though. – Armen Michaeli Nov 16 '20 at 09:46
  • This pattern may become invalid in the future. There's an [open bug issue on Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=977573) that aims to not fetch a resource described by a `link` with an invalid media attribute, and a patch which is ready to but waiting for this pattern to disappear. – cdoublev Jan 15 '21 at 11:17
17

Using media="print" and onload

The filament group recently (July 2019) published an article giving their latest recommendation for how to load CSS asynchronously. Even though they are the developers of the popular Javascript library loadCSS, they actually recommend this solution that does not require a Javascript library:

<link
  rel="stylesheet"
  href="/path/to/my.css"
  media="print"
  onload="this.media='all'; this.onload = null"
>

Using media="print" will indicate to the browser not to use this stylesheet on screens, but on print. Browsers actually do download these print stylesheets, but asynchronously, which is what we want. We also want the stylesheet to be used once it is downloaded, and for that we set onload="this.media='all'; this.onload = null". (Some browser will call onload twice, to work around that, we need to set this.onload = null.) If you want, you can add a <noscript> fallback for the rare users who don't have Javascript enabled.

The original article is worth a read, as it goes into more detail than I am here. This article on csswizardry.com is also worth a read.

Flimm
  • 136,138
  • 45
  • 251
  • 267
  • 2
    There is one caveat with that approach: it won't work with CSP script-src that does not use `unsafe-inline`. Example: `Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-eval'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.` – Cezary Tomczyk Jan 20 '22 at 10:21
6

The function below will create and add to the document all the stylesheets that you wish to load asynchronously. (But, thanks to the Event Listener, it will only do so after all the window's other resources have loaded.)

See the following:

function loadAsyncStyleSheets() {

    var asyncStyleSheets = [
    '/stylesheets/async-stylesheet-1.css',
    '/stylesheets/async-stylesheet-2.css'
    ];

    for (var i = 0; i < asyncStyleSheets.length; i++) {
        var link = document.createElement('link');
        link.setAttribute('rel', 'stylesheet');
        link.setAttribute('href', asyncStyleSheets[i]);
        document.head.appendChild(link);
    }
}

window.addEventListener('load', loadAsyncStyleSheets, false);
Mick F
  • 7,312
  • 6
  • 51
  • 98
Rounin
  • 27,134
  • 9
  • 83
  • 108
  • 1
    Are there any drawbacks of this method if we compare it say with the loadCSS approach? It seems, the classical code `var newStyle = document.createElement("link"); newStyle.rel = "stylesheet"; newStyle.href = "stylesheet.css"; document.getElementsByTagName("head")[0].appendChild(newStyle);` inside the ` – TecMan Apr 23 '18 at 13:22
  • 2
    @TecMan yes there are. As you can see, this function does it's job on `window.load` event. So, download starts when everything is downloaded. No luck there. You need non blocking loading as soon as possible to start. – Miloš Đakonović Nov 13 '18 at 13:17
6

Async CSS Loading Approaches

There are several ways to make a browser load CSS asynchronously, though none are quite as simple as you might expect.

<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
j0k
  • 22,600
  • 28
  • 79
  • 90
virender nehra
  • 470
  • 6
  • 12
5

you can try to get it in a lot of ways :

1.Using media="bogus" and a <link> at the foot

<head>
    <!-- unimportant nonsense -->
    <link rel="stylesheet" href="style.css" media="bogus">
</head>
<body>
    <!-- other unimportant nonsense, such as content -->
    <link rel="stylesheet" href="style.css">
</body>

2.Inserting DOM in the old way

<script type="text/javascript">
(function(){
  var bsa = document.createElement('script');
     bsa.type = 'text/javascript';
     bsa.async = true;
     bsa.src = 'https://s3.buysellads.com/ac/bsa.js';
  (document.getElementsByTagName('head')[0]||document.getElementsByTagName('body')[0]).appendChild(bsa);
})();
</script>

3.if you can try plugins you could try loadCSS

<script>
  // include loadCSS here...
  function loadCSS( href, before, media ){ ... }
  // load a file
  loadCSS( "path/to/mystylesheet.css" );
</script>
kamus
  • 797
  • 1
  • 6
  • 15
  • 4
    Example 2 loads Javascript, but the question is about loading CSS. Do you know if example 2 works also for CSS, if you change from ` – KajMagnus Sep 04 '17 at 14:36
3

If you need to programmatically and asynchronously load a CSS link:

// https://www.filamentgroup.com/lab/load-css-simpler/
function loadCSS(href, position) {
  const link = document.createElement('link');
  link.media = 'print';
  link.rel = 'stylesheet';
  link.href = href;
  link.onload = () => { link.media = 'all'; };
  position.parentNode.insertBefore(link, position);
}
Olivier Tassinari
  • 8,238
  • 4
  • 23
  • 23
2

Use rel="preload" to make it download independently, then use onload="this.rel='stylesheet'" to apply it to the stylesheet (as="style" is necessary to apply it to stylesheet else the onload won't work)

<link rel="preload" as="style" type="text/css" href="mystyles.css" onload="this.rel='stylesheet'">
0

If you have a strict content security policy that doesn't allow @vladimir-salguero's answer, you can use this (please make note of the script nonce):

<script nonce="(your nonce)" async>
$(document).ready(function() {
    $('link[media="none"]').each(function(a, t) {
        var n = $(this).attr("data-async"),
            i = $(this);
        void 0 !== n && !1 !== n && ("true" == n || n) && i.attr("media", "all")
    })
});
</script>

Just add the following to your stylesheet reference: media="none" data-async="true". Here's an example:

<link rel="stylesheet" href="../path/script.js" media="none" data-async="true" />

Example for jQuery:

<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" type="text/css" media="none" data-async="true" crossorigin="anonymous" /><noscript><link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" type="text/css" /></noscript>
Bradley
  • 132
  • 3
  • 15
  • I think the `async` attribute is ignored, as the script tag does not have a `src` to load asynchronously... or is it really useful here? Also can you elaborate a bit more on which value to use as a `nonce`? – Philipp Sep 05 '18 at 11:14
0

Please care to update the answer as all of the above fails to impress google pagespeed insights now.

According to Google this is how you should implement async loading of Css

 < noscript id="deferred-styles" >
        < link rel="stylesheet" type="text/css" href="small.css"/ >
    < /noscript >

<script>
  var loadDeferredStyles = function() {
    var addStylesNode = document.getElementById("deferred-styles");
    var replacement = document.createElement("div");
    replacement.innerHTML = addStylesNode.textContent;
    document.body.appendChild(replacement)
    addStylesNode.parentElement.removeChild(addStylesNode);
  };
  var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  if (raf) raf(function() { window.setTimeout(loadDeferredStyles, 0); });
  else window.addEventListener('load', loadDeferredStyles);
</script>
kaushik gandhi
  • 768
  • 8
  • 14
  • 1
    Likely a false positive (PageSpeed bug) if you're using the preload keyword approach I mentioned. https://github.com/filamentgroup/loadCSS/issues/53 – jabacchetta Jun 26 '18 at 15:30
  • 1
    UPDATE: Google has deprecated, and then shutdown, PageSpeed version 4 that had this problem - that was the version for which this async code was recommended. *Although I haven't tested in PageSpeed 5*: given the description of how they test now, and their support for "preload" tag of "link", [jabachetta's answer](https://stackoverflow.com/a/46750893/199364) is likely a better answer now, for Google. – ToolmakerSteve Oct 16 '19 at 12:13
  • 1
    @ToolmakerSteve Yes This answer needs to be moderated often. But this code still works to get you a 100 in Page Speed. – kaushik gandhi Oct 31 '19 at 06:37
  • It still works in the newest pagespeed tool and I am getting 100 as of 10th AUG 2022 – kaushik gandhi Aug 10 '22 at 03:05
0

I have try to use:

<link rel="preload stylesheet" href="mystyles.css" as="style">

It works fines, but It also raises cumulative layout shift because when we use rel="preload", it just download css , not apply immediate.

Example when the DOM load a list contains ul, li tags, there is an bullets before li tags by default, then CSS applied that I remove these bullets to custom styles for listing. So that, the cumulative layout shift is happening here.

Is there any solution for that?

n2lose
  • 141
  • 4
  • 14