Merging and minifying CSS is a standard practice in the performance optimization world which can help significantly speed up a site as well as increase page score. They help reduce payload size and minimize the number of server requests which is always going to have positive impact on the overall performance. Magento offers those tools out-of-the box and they should really be enabled by default (Configuration > Advanced > Developer > CSS Settings) on any Magento production site.

Keeping asset sizes small

While utilizing Magento built-in tools definitely helps, even on fairly simple projects the size of minified and merged CSS can amount to at least a couple hundred of kBs which. Because CSS by default is downloaded before page render having large assets is not ideal in terms of performance because even though not as significant as non-optimized images, it can still have a substantial impact on page loading speed and Page Speed score. Page Score Insights reports this issue under Eliminate render-blocking resources rule and suggests to keep request counts low and transfer sizes small by utilizing performance budgets.

Linting your CSS during development

Creating strict budgetary restrictions for your assets is indeed the best practice but it's definitely not easy in case of more complex projects that are being worked on by many developers, all with various degree of experience. For that reason, every team should have a set of rules in place that helps developers adhere to standards that will help maintain high quality of code and reduce the size assets at the same time. At Rocket Web, we use our own process for live checking CSS code after each file save, to catch any potential issues that violate our rules. This process is called linting and we use it as a gulp task in our FED development workflow which is defined in a local gulpfile. To run it, we simply start development task using:

$ gulp dev

When we change any of the CSS files in the theme folder a linter is run before LESS is compiled to CSS warning of any issues. Rules, such as indentation, max-nesting-depth or selector-max-id can be defined in one of the configuration files.

You can also trigger the linting tool ad hoc by running:

$ gulp less:lint

Advanced optimization technique

If performance is top priority on your project and you want to take the Eliminate render-blocking resources rule to the limit there's a couple of additional things you can do. You should start with loading the main css files (styles-m, styles-l) asyncronously. There's multiple ways to do that but we like to use the Filament Group's method which doesn't rely on any external library:

<!-- Magento_Theme/templates/root.phtml -->
<link rel="stylesheet" href="<?= $this->assetRepo->getUrl('css/styles-m.css') ?>" media="print" onload="this.media='all'" />
<link rel="stylesheet" href="<?= $this->assetRepo->getUrl('css/styles-l.css') ?>" media="print" onload="this.media='(min-width: 768px)'" />

You'll also need to remove those two CSS files from global layout in default_head_blocks.xml:

<!-- Magento_Theme/layout/default_head_blocks.xml  -->
<remove src="css/styles-m.css" />
<remove src="css/styles-l.css" />

Next step is to define common entrypoints for your website (HP, PDP, product listing page etc.) and create separate CSS files specific to those pages that contain only the rules that are being used on those pages and nothing more. Those files can be as small as 10-20% of the merged CSS file so quite substantial savings when it comes to reducing payload. This task can be performed manually, but as per usual, there are tools that help us automate this process that will save a lot of time. On our projects, we use another gulp task defined in the local gulpfile that takes config values (base url and entrypoints) from uncss.js and main styles from pub/static folder as input and generates appropriate CSS files. Those files are saved in theme's web/css folder with names that correspond to the entrypoints given (eg. styles-l-hp.css, styles-m-hp.css) and are automatically copied over to pub/static folder after static content regenaration (eg. during deployment to production). All of this is triggerred using the simple command:

$ gulp css:uncss

All you need to do next is load those newly generated CSS files in the corresponding pages layout files:

<!-- cms_index_index.xml -->
<head>
    <css src="css/styles-m-hp.css" />
    <css src="css/styles-l-hp.css" />
</head>

When you load the HP with the optimized stylesheets for the first time everything should look the same but if inspect Network tab in Dev Tools you'll notice the main stylesheets (styles-m.css, styles-l.css) do not block page rendering as they're loaded asynchronously. Page load speed increase may or may not be noticeable depending on the size of your assets but you'll definitely get a better Page Speed score.

Conclusion

While Magento offers some basic tools for CSS optimization, it always pays off to pursue better ways to optimize your assets. In our case, linting our CSS and using combination of async loading and uncss to reduce the size of core stylesheets works best. There are other ways to furhter optimize CSS, like replacing Magento's minification tools with custom ones (eg. cssnano) or inlining your above-the-fold CSS inside document's <head>. Possibilities are endless but we always find that the simpler the tool is the more effective it is. As long as your focus is on site's performance, you'll find the best approach is a matter of experimentation and a lot of audits.