universal: inlineCriticalCss is slow

🐞 Bug report

What modules are related to this issue?

  • aspnetcore-engine
  • builders
  • common
  • express-engine
  • hapi-engine

Is this a regression?

No

Description

Recently I tried to enable inlineCriticalCss on our project. And had to roll it back quickly - SSR times jumped sky high.

styles.css in our project is around 250KB. This includes Angular Material (core + theme) and a number of old-style components that have not been converted to styleUrls yet. However even if I strip it down to bare minumum - it’s still ~100KB (majority of that are Material styles).

HTML is also somewhat significant - 200KB (after I stripped it off of transfered state), and contain 1500 DOM nodes.

Profiling shows that most of the css inlining time is spent on findOne call.

My understanding is that critters has complexity roughly O(N * S), where N is number of DOM nodes, and S is number of selectors in the stylesheets. That’s because it queries every selector against the DOM. And our stylesheet (the minimal version) has 1000+ selectors. The regular prod version of the stylesheet - 2600 selectors.

I’m wondering if it could be improved by grouping selectors and discarding a whole group if the top-most selector was not found. E.g if we checked .mat-button selector and didn’t find any matching node - there is no point in looking for more specific selectors like .mat-button .mat-button-focus-overlay

🔬 Minimal Reproduction

🌍 Your Environment


Angular CLI: 11.2.12
Node: 12.14.1
OS: darwin x64

Angular: 11.2.13
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... platform-server, router
Ivy Workspace: Yes

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1102.12
@angular-devkit/build-angular   0.1102.12
@angular-devkit/core            11.2.12
@angular-devkit/schematics      11.2.12
@angular/cdk                    11.2.12
@angular/cli                    11.2.12
@angular/material               11.2.12
@nguniversal/builders           11.2.1
@nguniversal/express-engine     11.2.1
@schematics/angular             11.2.12
@schematics/update              0.1102.12
rxjs                            6.6.3
typescript                      4.1.5

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 24
  • Comments: 73 (27 by maintainers)

Most upvoted comments

I did existing tools research and wanted to share some thoughts here. Most tool work similarly to Critters - they individually test each selector against full dom.

However, PurgeCSS (https://github.com/FullHuman/purgecss) uses a different approach: it extracts all class names, tag names, ids and attributes from html, stores them in sets. And then checks whether all parts of a particular css selector are included in those sets. Since set lookup is constant, it gives a solid CPU boost. The tradeoff is that the result is less precise: some complex CSS rules are incorrectly marked as “used”, while they do not match the actual DOM tree shape.

Critters makes a lot of sense with pre-rendering. However, SSR suffers a lot from its CPU-bound computations. Maybe we could use different tools for those two use cases? Less precise but faster - for SSR. And more precise but slower for pre-rendering.

Thank you @alan-agius4 & @ubaidazad. 💯 👍 Temporarily disabling the feature in server.ts with inlineCriticalCss: false worked for us. We await future speed improvements.

At FloSports, we have a content-rich web app and several news articles with chunky HTML tables (~150KB) of athlete/team data (imagine an Excel dumps worth). Those articles really magnified the speed issue with +20s renders. In addition to these articles, in general, we saw an increase in SSR latency of 2.1x to 3.2x across all of our pages.

We didn’t notice the issue until it deployed to production. Below you can see when we were alerted via Datadog of an anomaly and debugged a bit before manually rolling back the deployment. image

Thanks @alan-agius4 this actually helped

in server.ts

server.engine('html', ngExpressEngine({
  inlineCriticalCss: false,
  bootstrap: AppServerModule,
}));

Isn`t fix in angular v15?

Nope, unfortunately not. I am on NG 15.1 inlinceCSS on -> 5.6-8.9s inlinceCSS off -> 0.5-1.1s

In version 16.1.0-rc.0 we have updated critters which contains several performance improvements.

If you disable inlineCriticalCss on the server, make sure you also disable this in the browser build by changing the optimization option in angular.json

See : https://angular.io/guide/workspace-config#optimization-configuration.

For anyone having the flickering when they set inlineCriticalCSS: false in server.ts, just do this to fix it:

"optimization": { "scripts": true, "styles": { "minify": true, "inlineCritical": false }, "fonts": true }

Hello @juanico18 ,

I have migrated to Angular 16, yes, and I had this setting prior the migration (on Angular 15) and Angular 16. Then I migrated to standalone components but before I did have the classic module configuration. In terms of performance improvements, I have noticed less amount of bytes being transferred over the net because there is less javascript, but regarding speed, perhaps what I did notice is bit more stability with the hydration process that has been improved in SSR. The PageSpeed score I have seems to improve, but I have to say that I have also applied more optimizations to my APP so I don’t think they are fully related to either standalone or Angular 16 itself.

PurgeCSS is compatible. You can apply it as part of the process.

The “only” test I have to conduct now is to revert back to inlineCriticalCss: true and see the results with the current configuration. That is something we have planned to do in a month or so.

Regards

Hopefully @alan-agius4 you guys get that stable real soon so we can begin the upgrades!

The stable release is scheduled to happen next week.

Do we still have to set "inlineCritical": false in the server.ts to get the best performance possible? I can see that’s not present in the .zip for the universal example in angular.io and I don’t know if that example is fully up to date. Could you confirm @alan-agius4 ? Thanks!

Isn`t fix in angular v15?

In order to avoid any confusion, guys could you please clarify the way of fully disabling the inlining of critical css? I say that because I don’t whether setting in the server.ts is “just” enough or you have also to configure it in the angular.json. Would be nice to know all sections related that need that.

Many thanks!

@CarlosTorrecillas Could you please suggest some tips to address those feedbacks? image

@CarlosTorrecillas, there are no further performance improvements planned for critical CSS inlining at the moment.

@alan-agius4 do you think after we upgrade to v16.1.0 we can set inlineCriticalCss to true on both the browser and the server or do you guys expect to have further performance improvements?

@brjadams, no, please see the LTS policy.

Hi @poonga ,

Without looking at the code / setup, I would recommend several checks to try mitigating the errors / bad score here:

  1. Reduce initial server response time: 1) Take a look at your server, check for high CPU / MEM processes to see if there is anything obvious or something you can improve. 2) Check if your initial bootstrap of your application is inefficient. Make sure you use lazy loading and also you only load the components you need when loading the app. 3) Check long running API calls that may be affecting this initial load.

  2. Reduce unused javascript: Check your bundle size and whether you are building them correctly. To be honest, most of my apps are now complaining about it and I have been optimizing code for a while without much success.

  3. Serve images in next gen format: Make sure your images have good format such as webp and also have the right size and have defined the height and width accordingly in you style or html.

  4. Efficiently encode images. Never came across that one.

  5. Ensure text remains visible during webfont load: In recents page speed tests of my apps that had to do with the way of loading Google fonts. I used to have webfontloader (JS) however I switched to do it via <link and also doing preconnects to the Google servers. There is a lot of examples on the web.

  6. Does not use passive listeners to improve scrolling performance. Never came across that one before but I guess you have hooks somewhere that are slowing down the app when you scroll / mouse wheel?

  7. Serve static assets with an efficient cache policy: 1) Make sure your web server is adding the cache header to the images you are serving so that they get cached in the client. That will depends on the type of web server for the specific setup but there is a lot of documentation for all available servers out there. 2) If you’re using a CDN make sure you have the cache policy correctly set up.

  8. Avoid an excessive DOM size: Check if you can optimize your components so that the HTML can be reduced. As a good practice I tend to split my pages into really tiny components so that I can run checks on that quite easily. Good parts to check are ngFors where you create many elements. If that was not something you could reduce, at least try to apply above the fold / uner the fold techniques to reduce the amount of elements loaded at the bootstrap of the app. That way you will be adding to the DOM progressively which is more performant.

  9. Minimize main thread work: Again, check the overall workflow of the application. Check listeners, timeouts, api calls, etc. Try to see if you can optimize or make some concurrent calls, reduce listeners or implement them in a different way. If you’re using SSR which I believe you do, make sure you don’t run unnecessary code in your server side, such as animations, times, or simply rendering code that you may not need

Hope this helps. This is my two cents based on my experience with Angular SSR.

@poonga in my case I did have very little flickering. Still present but sometimes you don’t even notice. For me the issue was the lighthouse ranking in the page speed score for Google which was improved

If you disable inlineCriticalCss on the server, make sure you also disable this in the browser build by changing the optimization option in angular.json

See : https://angular.io/guide/workspace-config#optimization-configuration.

Hi @amakhrov,

Thanks for trying the feedback. I’ll pass this to the Chrome SDK team since they are responsible of critters.