Generate critical CSS

This option is now available under Configuration settings. When enabled, a small CSS file is generated for each page, containing only the styles needed to display what is called "above the fold" content. By loading these styles ahead of all the other styling assets, the time it takes for the browser to display its first meaningful content is reduced significantly.

This functionality relies on a NodeJS package called critical for figuring out which styles to include.

Depending on your server, you might need to install additional dependencies to run headless Chrome:

sudo apt install libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxi6 libxtst6 libnss3 libcups2 libxss1 libxrandr2 libgconf2-4 libasound2 libatk1.0-0 libgtk-3-0

Required conditions for server push

IMPORTANT: As of November 2020, the HTTP/2 server push implementation is marked as deprecated by Google Chrome. We'll have to rely exclusively on HTML preload tags in the future.

These conditions assume that you're using Nginx to serve your website.

  • HTTP2 needs to be activated
  • HTTPS configured with a trusted SSL certificate (like Let's Encrypt or Cloudflare; self-signed won't work!)
  • The http2_push_preload on; directive in your server block

The actual push headers will be set by PHP.

Activating critical CSS

You can enable or disable critical CSS generation under Configuration > Performance. If you're using context aware Configuration settings, then you can control this functionality per context.

Excluding pages

Pages that don't need critical CSS (like an XML sitemap or PDF download) can be excluded based on their template. You can enter a comma separated list of template IDs under Configuration > Performance > Non-critical templates. Templates in the category Global are excluded by default.

Sharing critical CSS across pages

By default, a critical CSS file is generated for each resource on save. This might not always be what you want. Some resources can easily share a single CSS file because their layout is virtually the same on each page.

For these cases, you can designate a template as shared under Configuration > Performance > Shared critical templates. A single critical CSS file will then be used, with the template name as filename.

(Re)generate CSS files for existing pages

And now for the trickiest piece of the puzzle: if you change any styling that might affect above-the-fold content in your project, you also need to update all the critical CSS files. You can create a utility snippet for this:

Be careful when running this on a live server! Read the performance caveats first.

[[!pdoMenu?
    &context=`web`
    &parents=`-123`
    &level=`0`
    &tplOuter=`navWrapper`
    &tpl=`criticalRowTpl`
    &showHidden=`1`
    &showUnpublished=`0`
    &limit=`10`
    &offset=`0`
]]

The content of the row tpl:

[[!generateCriticalCSS? &id=`[[+id]]`]]<br>
[[+wrapper]]

Performance

The above utility snippet triggers a series of Gulp tasks in the background. This requires a significant amount of system resources. As in: will completely fry your 8 CPU cores if you run more than around 10 tasks in parallel.

The only way around this is to generate the files in small batches. This can be done like in the example, by setting a limit and then using offset to generate the next batch. But that's obviously a less than ideal scenario, especially on larger projects. A better way would be to add these operations to a task queue, so they can be executed one after another (or in very small batches) in the background.

Romanesco has that option built in. If you install and configure the Scheduler extra, the GenerateCriticalCSS plugin will detect it and add each resource to the queue as a separate task. See scheduling tasks in MODX for more info.

Tasks are run via a cronjob, at most once every minute. You can tell Scheduler to pick up multiple jobs on each run by adding or changing the scheduler.tasks_per_run system setting.

Alright, you can relax now.

Further reading

https://stackoverflow.com/questions/32759272/how-to-load-css-asynchronously