question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[RFC] Eliminate Render Blocking Requests

See original GitHub issue

Authors: Alan Agius (@alan-agius4) Status: Closed Closing Date: 2020-09-22

Summary

We’re proposing to eliminate render blocking requests loading by:

  • Loading CSS files asynchronously in a single-page application.
  • Inline critical CSS in Angular applications which use Angular Universal Server Side Rendering Pre-rendering, App Shell and Angular Client Side Rendered Applications.
  • Inline Google Fonts and Icons.

At the moment there is no easy or streamlined way to identify, extract or inline critical CSS in Angular Universal applications. We’re proposing to offer an out-of-the-box solution with little or no configuration needed.

Motivation

CSS files are render-blocking because the browser must download and parse these files before starting to render the page. This makes CSS files a bottleneck when they are large or when having poor or limited network connectivity. Each of these files will result in a penalty on the Performance Score of your application #17966.

We can reduce this render-blocking time and at the same time improve the first contentful paint (FCP) by extracting and inlining the critical CSS and loading the CSS files asynchronously.

Proposal

Load CSS files asynchronously

In most cases JavaScript bundles will take a longer time to download, parse than for Angular to bootstrap and start rendering the first component thus the chance of having Flash of unstyled content (FOUC) is relatively low.

We are proposing to introduce an experimental async CSS loading use the “media” technique which can be opted-in/out via an option angular.json

Before

<link rel="stylesheet" href="styles.css">

After

<link rel="stylesheet" href="styles.css" media="only x" onload="this.media='all'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>

CSS files budgets

CSS files are great for code-sharing, but other than that they are a bottleneck to achieving great performance. Two of the main reasons for this is that they are render blocking and might contain dead-rules.

Since a CSS file is not strictly associated with the components loaded on the page, a number of CSS declarations that get downloaded and parsed will remain unused by the view that was rendered.

Having render blocking and/or dead-rules will cause performance score penalties in Lighthouse.

We propose to add two new bundle budgets allStyle and anyStyle:

  • anyStyle: any given external CSS files
  • allStyle: cumulative size of all external CSS files.

Inline Google Fonts and Icons

We are proposing to introduce an experimental optimization for fonts which can be opted-in/out via an option in angular.json.

During build time we will parse the index.html, download the content of stylesheets originating from https://fonts.googleapis.com/… and inline their content.

This eliminates the extra round trip that the browser has to make to fetch the font declarations, which improves LCP, reduces FOUC, and also unlike other approaches doesn’t prohibit Angular applications from taking full advantage of font-display: optional.

Before

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

After

<style>
  @font-face {
    font-family: 'Material Icons';
    font-style: normal;
    font-weight: 400;
    src: url(https://fonts.gstatic.com/s/materialicons/v55/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');
  }

  .material-icons {
    font-family: 'Material Icons';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
  }
</style>

In case the application needs to support Internet Explorer 11 which will be determined via the browserslist configuration, the woff1 definition of the font will also be inlined.

Extract Critical CSS

The most generic which requires zero to no configuration from the developer is to inline critical CSS as a post-rendering step using existing projects such as: penthouse, critters and critical.

The above mentioned tools take different approaches to extract critical CSS. The approach that Critters uses is the best fit for our use cases. The main reason for this is that Critters doesn’t use a headless browser but uses a JavaScript DOM (JSDOM) to render the page content which makes it faster compared to the other tools and hence it makes it valuable to be used as a build time and runtime option. The main trade-off of this is that it doesn’t predict the viewport and inlines all the CSS declarations used by the document.

SSR

In Angular Universal SSR we cannot use Critters directly because this is a Webpack plugin and rendering of Angular Universal pages for both dynamic and static applications happens outside of the Webpack build. Therefore, a more decoupled version of Critters core functionality would be needed.

We’ll run Critters during runtime. When a request hits the server and the Angular SSR page is rendered, we will run Critters as a post-rendering phase to extract the critical CSS and inline in the final HTML response.

App-Shell & Pre-rendering

As a post-rendering phase during build time, we’ll run Critters to extract the critical CSS of the rendered page and inline the contents in the HTML document.

CSR

In Angular CSR, we cannot extract and inline CSS because we are unable to run it in a Node.Js environment, However, it is common for CSP’s to have a custom loading experience outside of Angular context defined in the index.html file. Therefore, we are proposing to extract and inline CSS for this use case to reduce the risk of FOUC even further.

Alternatives

Below are some alternatives that we have considered but deemed less useful / less feasible compared to the main proposal.

Annotating critical CSS

Annotating critical CSS with a comment and tools such as postcss-critical-split will extract these into a separate file which can later be inlined.

/* critical:start */
header {
  background-color: #1d1d1d;
  font-size: 2em;
}

.aside {
  text-decoration: underline;
}
/* critical:end */

The drawback of this is that It will be up to the developer to determine which CSS declarations are critical or not. Developers will also not be able to annotate critical styles which are not part of the application such as when depending on a vendored UI framework library such as Material, Bootstrap etc…

Hence this approach is more complex, has a bigger learning curve and is error prone.

Using headless browsers based extractors

Penthouse is a critical CSS extractor and can do so for non SSR’d applications. This is because under the hood it uses puppeteer to generate the critical CSS.

The main drawback of this is that this approach will be different from what’s proposed for Angular Universal and is slower.

Include CSS files in app root component

Another approach would be to include the global styles in the app root component.

@Component({
 selector: 'app-root',
 templateUrl: './app.component.html',
 styleUrls: [
   './styles.css',
   './app.component.css',
 ],
})
export class AppComponent { }

The main drawback of this is that the entire contents of styles.css will be inlined in the HTML page when using Angular Universal or App shell.

DNS-Prefetch and Preconnect Hints

Add dns-prefetch and preconnect hints for for https://fonts.gstatic.com to initiate DNS resolution and a connection.

Additional Resources

Open Questions

  • Should these features be on opt-in or opt-out bases?
  • Should we add the bundle budgets to existing applications? If yes, what should be the default threshold for warnings and errors.
  • Should we add the bundle budgets to new applications? If yes, what should be the default threshold for warnings and errors.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:91
  • Comments:18

github_iconTop GitHub Comments

8reactions
adnanebrahimicommented, Sep 8, 2020
6reactions
alan-agius4commented, Sep 9, 2020

Hi all, thanks for the excellent feedback.

Agree with @adnanebrahimi. The easiest way to remove unused CSS in Angular apps is to use @angular-builders/custom-webpack with the purgecss-webpack-plugin add-on (which also isn’t a perfect solution, as it breaks cache-busting hashes), which is way too complicated as it requires knowledge of Angular Builders, Webpack, and PurgeCSS.

For such a huge savings in file-size, its really strange that something similar isn’t supported out of the box by Angular.

@adnanebrahimi & @elliotleelewis, unused CSS removal is a very interesting topic and is on the radar. However, it gets tricky if your application content is dynamic where the parts of the content gets retrieved from an API/Database or classes are interpolated example: class-{{category}}. as in such cases, used CSS will removed. So it’s a double edged sword.

While unused CSS is definitely useful, it doesn’t help reducing or removing render blocking requests. It’s important to remember that a 0Kb CSS file still impacts performance negatively, because of the time spent for a connection to be estimated and the file to be downloaded all while rendering is blocked.


In order to understand what is being proposed, it is important to define “Critical CSS”. I haven’t done testing, but I would guess that critters and critical have different methods of defining the Critical CSS that they inline. I would imagine the same thing for Penthouse. Further, based on my understanding of how it is calculated would mean that each route in the app could have distinct Critical CSS.

  • What is meant by Critical CSS?
  • Can the Critical CSS of an app change per route? If I have 20 statically analyzable routes in my Angular app, can I have two potential Critical CSS definitions? If yes, then generating the Critical CSS gets significantly more complex and time consuming.

@aaronfrost, indeed that each tools has a different methods of detect Critical CSS. In Critters terms, it’s the entire document structure that is rendered. From their readme

It’s a little different from other options, because it doesn’t use a headless browser to render content. This tradeoff allows Critters to be very fast and lightweight. It also means Critters inlines all CSS rules used by your document, rather than only those needed for above-the-fold content.

When it comes to Critical CSS per route, this is what’s being proposed for SSR and pre-rendering. For SSR, critical CSS will be extracted and inlined during runtime (Given a more lightweight and non Webpack plugin of Critters is available.), while for pre-rendering this shouldn’t impact much the build time due to the parallel nature of the build.


Small note: I think using a faux media type for async loading CSS will download the CSS file anyway on some browsers.

Read more:

https://bugs.chromium.org/p/chromium/issues/detail?id=977573

https://www.filamentgroup.com/lab/load-css-simpler/

@kevinfarrugia, I think you meant that the CSS file will not be downloaded when using an invalid media. While the article from loadCss suggests not to use an invalid media attribute. it’s actually what they use internally: https://github.com/filamentgroup/loadCSS/blob/c14df53ebed55d4d06490d19bbc0265e2af19b98/src/loadCSS.js#L35.

That being said, It does look like using media="print" will have the same effect as media="only x".

Read more comments on GitHub >

github_iconTop Results From Across the Web

How To Eliminate Render-Blocking Resources
Discovering critical render-blocking resources and repairing them can significantly boost your SEO. Read on to learn how.
Read more >
Remove render blocking javascript in rails with react-rails gem
Therefore in my experience there is no way around the blocking Javascript. ... and other assets so they do not block on subsequent...
Read more >
Eliminate render-blocking resources - Chrome Developers
The goal is to reduce the impact of these render-blocking URLs by inlining critical resources, deferring non-critical resources, ...
Read more >
RFC 4579: Session Initiation Protocol (SIP) Call Control
RFC 4579 SIP CC Conferencing for UAs August 2006 This document presents the basic call control (dial-in and dial-out) conferencing building blocks from...
Read more >
RFC 3261 SIP: Session Initiation Protocol - IETF
When a client cancels a transaction, it requests that the server stop further processing, revert to the state that existed before the transaction...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found