WordPress 5.0 and Gutenberg: A better way to handle CSS overload
As you may know, WordPress 5.0 introduced a new editor experience (for those that haven’t decided to explicitly disable it), and the response has been mostly “ok, cool.” from users, despite the controversial release schedule, and the decision to punt many accessibility and performance issues to subsequent patch releases.
And while I won’t try to overcompensate for the problems that have been pointed out, I will say that I mostly like the new editor experience, both from the perspective of a user (I’m writing this post in Gutenberg) and a developer.
And as a developer, I can’t help but notice that there is a pretty predictable problem staring down its nose at us.
The problem is front-end performance, and to be honest, it’s not really Gutenberg’s fault.
Some background
Gutenberg is a “block based” editor. Every piece of content in your post/entry is a block. Paragraphs, images, headings … all the normal stuff you’d put into entry content is a block.
And this opens up a world of opportunity for users to activate, as needed, new types of blocks.
Need a contact form on your contact page? There’s a block for that.
Need an image gallery? There’s a block for that.
Need a bunch of useful blocks in a single plugin? There’s a block library for that.
Now, in order to meet your needs in creating the kind of content you want, you may end up with several block plugins installed at once, in addition to the plugins you’re already using. Ignoring, for now, the risks of simply having too many plugins active, I want to focus on the bigger problem.
But first …
Waterfalls
When testing the performance of a website as it would appear to a user, developers do something called a waterfall analysis.
It’s basically a chart of every resource your site requires in order to load fully, and the time it took for each of those resources to download.
It’s a really good visual representation of page weight.
I won’t explain waterfalls in too much detail; there are much better articles for that than this one. But to give a brief summary, each resource download your page uses is represented by a row. Each row has a colored line (far right) that indicates when the asset started downloading, and how long it took. Obviously, bigger files from slower servers are going to take longer, whereas small files from fast servers (like CDNs) will take less time.
In addition to helping you locate assets that are loading slowly (due to size or server speed), the waterfall shows you another useful metric: what resources are blocking other resources from downloading, and ultimately, blocking the page from rendering.
Although there are a few types of render blocking resources, for the purposes of this article, we’re going to focus on the most relevant one, stylesheets (CSS).
Render blocking stylesheets
Because there is currently no way to prevent stylesheets from blocking the render of a page (and for good reason, FOUC is no fun), we’ve basically just had to accept that our stylesheets were going to halt the render of our page until they’ve all downloaded.
Essentially, until all of the CSS is downloaded, nothing on the page will render.
So we invented little tricks to help minimize this issue, like minifying our CSS, combining all the CSS into a single stylesheet (where possible), and relying heavily on browser caching.
And when you have total control over your site, these aren’t terrible options.
But an extensible CMS will complicate things. Your theme loads some CSS, plugins load CSS, WordPress core load some CSS.
It’s not at all surprising to see a WordPress site loading 10+ stylesheets, and that’s just going to get worse with Gutenberg blocks needing their own CSS to render properly on the front end.
What’s worse, these stylesheets are being loaded regardless of whether they’re needed. If a page isn’t using a particular block, the block plugin will still likely load the CSS for that block.
But even if the block plugin were aware that the page was using the block, and only loaded the CSS when the block was being used on a page, we still have a problem! When you register and enqueue a stylesheet in WordPress (the Right Way), it will output to the <head>
, as it is designed to do.
The problem is, now a stylesheet designed for a block that is used at the bottom of a blog post is blocking the render of the site header, or menu, or post title, etc.
Why should a block placed well below the fold affect the rendering of the site header?
Let’s Modularize
Conventional wisdom tells us that we need to serve as few CSS files as possible. Every HTTP request includes some network latency, and therefore reducing requests means less time until all our CSS is downloaded and the page is ready to render.
After all, if every CSS file is loaded in the <head>
, and blocks the render of the entire page, then why wouldn’t you just combine all your CSS into a single file, a single HTTP request?
Note: although you could make the argument that modularizing your CSS files provides a more efficient cache busting opportunity, and HTTP2 allows for asynchronous CSS downloads, latency is still an issue so it’s still best for performance to combine CSS in production.
But, a couple of months ago, Jason Cohen shared and interesting link to the engineers at WP Engine. An excerpt:
due to a recent change in Chrome (version 69, I believe), and behaviour already present in Firefox and IE/Edge,
CSS Wizardry<link rel="stylesheet" />
s will only block the rendering of subsequent content, rather than the whole page.
If all the stylesheets are linked in the <head>
, this new information doesn’t really change much. It’s still blocking everything after the link … every visual element on your site.
But what if we split CSS into multiple files, each corresponding to a piece of content that needed styling. And instead of linking them all in the <head>
, these CSS files would only be linked directly before the content they were designed to style was present.
It still blocks rendering, just like the traditional method, but because we’re linking the CSS file in the <body>
right before the content it’s designed to style, we don’t block rendering of content before that point in the source code.
The practical upshot of this is that we’re now able to progressively render our pages, effectively drip-feeding styles to the page as they become available.
CSS Wizardry
Put another way, we’re drip feeding styles to the page as needed.
It also has the added benefit of strategically blocking the rendering of any content which doesn’t have corresponding CSS dowloaded yet. No FOUC.
Time to first render
So why is this a big deal? Because page speed (the actual time it takes for your full site to load) and perceived page speed (the time it takes for a user to think your site has loaded) are very different things.
And it turns out, users don’t care that much about how long your site actually takes to load. They’re not watching the waterfall, they’re watching the screen.
Anything we can do to reduce the time a user has to wait until they first start to see things loading on our page is a win for visitor retention. Users don’t see the footer when the page first loads. And with more and more traffic coming from mobile, there’s even less that is actually seen when a page is first accessed.
This presents a big opportunity! We can decrease the time to first render, and delay the rendering of elements further down the screen until after their related CSS has been loaded.
So, Blocks?
Admittedly, this is hard. WordPress is very specific about the way it wants you to load your CSS files.
Currently, you have the option to register a stylesheet with one function, and another function enqueues it (it also will do the job of registration, but you should really register and enqueue separately).
When you enqueue styles this way, every stylesheet link gets output to the <head>
. Each stylesheet is registered with a unique handle, so the same stylesheet link doesn’t get linked more than once.
I’m not suggesting that we start linking our own stylesheets outside the WordPress way. But something does have to change.
What if you could register stylesheets the same way, but instead of enqueueing them to be output in the <head>
, you could call a function manually that would output the stylesheet link (with any dependencies) immediately before a block. Once a link is rendered, the WordPress stylesheet manager could make sure it doesn’t get rendered again.
As it turns out, you can!
Here’s a quick proof of concept (and a gist). It’s using the CTA block from Atomic Blocks as the example, but obviously production code would look a bit different.
// this snippet requires PHP 5.3+
add_action( 'wp_enqueue_scripts', function() {
wp_register_style( 'atomic-blocks/ab-cta', '/path/to/atomic-blocks/css/ab-cta.css', array(), '1.0.0' );
} );
add_filter( 'render_block', function( $block_content, $block ) {
if ( 'atomic-blocks/ab-cta' === $block['blockName'] ) {
ob_start();
wp_print_styles( $block['blockName'] );
$block_content = ob_get_clean() . $block_content;
}
return $block_content;
}, 10, 2 );
You essentially register the modular CSS as you normally would. Then, leveraging the render_block
filter, you can output a stylesheet link “just in time”, so that the content gets CSS to style it before it gets rendered, but the CSS doesn’t block the rendering of anything before it in the source.
This is legitimately amazing. By using wp_register_style()
and wp_print_styles()
, we are working within the existing WordPress stylesheet loader, and we get all the benefits of doing so. But by not enqueueing the styles, and instead printing them directly where we want them, we can output the stylesheet links right before the block they style, and only if the block is actually used. Yes, we have to use output buffering, but that’s sometimes unavoidable, and in this case, it’s worth it.
Themes?
This method isn’t limited to blocks. Entire themes could be built this way. Instead of a master stylesheet that styles everything, use separate stylesheets for each section of your site and output the stylesheet link right before the section it styles.
Has it ever bothered you that WordPress themes load CSS for the comments section on pages with comments disabled, or on archives, or on the homepage? Yeah, me too.
By only linking a stylesheet when it’s needed, you only load CSS for sections that actually exist on the page. This can significantly decrease total page weight, in addition to the decreased time to first render mentioned before.
Plugins
Plugins have the exact same opportunity that themes and blocks do.
It’s really not complicated. Simply register your stylesheet as you normally would, then use wp_print_styles()
to output the CSS as part of any rendering functionality your plugin uses. If your plugin provides a widget, then make sure the widget styles get output when the widget renders … don’t enqueue the stylesheet like you used to!
Gotchas
So what’s the catch?
Currently this method works in all major browsers except Safari. It doesn’t break or anything, it just treats each linked stylesheet as if it were in the <head>
. That is, each linked stylesheet is render blocking for the entire page.
So basically, in Safari, we end up with the same outcome we’d have if we didn’t implement this method at all.
Sure, by using this method, you might sacrifice some performance in Safari (until they fix things) by not concatenating all your CSS into a single file, but by having a stylesheet for each major section or content type, and only linking it when that section or content type is actually in use, you also gain some performance by reducing your total page weight.
One possible issue is that on pages where we’re not actually reducing total page weight – a page that uses a lot of sections, blocks, etc. – we might technically be increasing the total pageload time by modularizing our stylesheet loading method. It’s possible that search engines like Google, who prioritize speed, might penalize you for choosing time to first render over time to full render. But in my opinion, this is unlikely.
Am I wrong?
the best way to get the right answer on the internet is not to ask a question; it’s to post the wrong answer.
Ward Cunningham, Cunningham’s Law
So, what did I miss? I want to kick off a discussion of this problem, the proposed solution, and also kick around other ideas.
I’d also love to see actual metrics for this method. I haven’t been able to do a lot of testing, so this post is pretty theoretical when it comes to the performance benefits I’m predicting.
If you want to discuss this on Twitter, you can find me at @nathanrice