Hugo Inline CSS Optimization for Google PageSpeed

If you’ve ever run the Google PageSpeed test on any of your sites you’ve probably seen it complain about loading external CSS files and asking you to use it inline in your HTML.

Now if you’re like me and you use different HEAD partials depending on the section of your Hugo site, you already know that inlining the CSS in your different HEAD partials will be a maintenance nightmare.

The solution is pretty simple. Just create a new partial under /layouts/partials, just call it styles.html. Then open a <style> tag, paste the contents of your CSS file and then type </style>. Hit save.

We can call our new CSS html partial like this {{ partial "style.html" . }} just before the closing </head> tag.

Et voilà! Inline CSS that will keep Google PageSpeed happy and just one file with all your styles to update whenever you need without hassle!

7 Likes

HI @alexandros

This is a great tip and thank you for sharing! I’m actually doing something similar with the new docs site. Here is the partial:

https://github.com/rdwatters/hugo-docs-concept/blob/master/themes/hugo-docs-concept/layouts/partials/style-embed.html

One thing you might want to try to for a little bit more automation is writing to style.html as part of your frontend build process. I’m currently using Gulp, but this will give you an idea of the workflow. The nice thing is that you can still make changes during local dev and have Hugo do its LiveReload magic since it watches for the change in the partial:

https://github.com/rdwatters/hugo-docs-concept/blob/master/themes/hugo-docs-concept/pipeline/gulpfile.js#L31-L40

Cheers!

Keep in mind that it is not best practice to put your entire stylesheet inline in your head. It is best practice to put your “critical” (above the fold only) CSS in the head, but because browsers will cache your external CSS, it should be only the critical. There are tools to discover your critical CSS and output it. And, frankly, this is one of the lower priority performance tweaks, in my view.

Good points and agreed! You can also keep all style in your head as long as it’s small enough to not incur more HTTP requests, which is the real perf killer. If you do keep your entire CSS in the head of the document but it takes 4 round trips, you haven’t really done yourself any favors. Caching stylesheets seems great, but the difference in the context of GPU rendering is negligible, regardless of whether it’s sent over all at once with the markup or pulled from the browser cache…

All of this is moot if your homepage also has a 3.4mb photo or bloated, render-blocking JS, but I digress…

[EDIT]

So, you have your page there lickity-split, but it’s a waste of energy if your transitions are a jank-a-thon. Nice article on Smashing for anyone interested:

I really have no idea what you’re talking about when you talking about GPU rendering of a stylesheet, but I think you might enjoy reading about HTTP2 multiplexing.

1 Like

I’ve read about it quite a bit and agree it’s awesome to think that we’ll be able to handle multiple asset requests with a single TCP connection! But the Hugo audience is quite international, and I try not to assume that everyone is serving over TLS. I think we are both looking forward to a time where HTTP2 is the standard and Internet Explorer is dead :smile:

True. But my stylesheet is not that big. I keep it simple.

1 Like

Actually, @alexandros I’m not all that against the practice, but I am against Google Page Speed driving people to do things that may or may not be the best way to do it overall, for the sake of a score.

In fact, to make theming easy, I put a bunch of variables, like colors in a data file and loop through them in a partial to create styles in my head. Though I do it for theming purposes (to avoid users having to worry about an asset pipeline) rather than performance.

3 Likes

I agree with you 100% on this. Google keeps making new rules and changes all the time. A few years back external CSS wasn’t “render blocking”. But unfortunately we have to try and take Page Speed into account since Google penalizes slow websites on the SERPs (according to what they define as fast).

3 Likes

HTTP/2 multiplexing sounds great. I dropped a tutorial in the Hugo docs for those who’d like to get HTTP/2 setup with Hugo. Meanwhile, for snappy mobile experiences, we need to inline critical CSS (regardless of what PSI or Lighthouse say). For non-critical CSS, you can get a jump on performance loading it alongside JS in parallel.

By the way, for those of you who did what I did and used critical-[styles].css.html for your CSS partials, here’s a related issue you may want to check out.

1 Like

Performance with HTTP/2 would be even better if S3 allowed for adding the server push header. In the test of that SmashingMagazine article, server push performed better than inlining CSS while giving more caching benefits.

2 Likes

@jhabdas I already read your article…and loved it, especially the comments :smile:

I used to think multiplexing was a panacea until I read the following article from KA:

http://engineering.khanacademy.org/posts/js-packaging-http2.htm

Has it really been a year and a half since that article was published? Sh&^. I gotta get out more…

I’m curious to hear your thoughts on AMP, but maybe that’s for a different thread.

Also, :thumbsup: :thumbsup: :thumbsup: on fetch-inject; looking forward to geeking out over this over the weekend :sunglasses:

1 Like

This is amazing stuff, guys. I’m soaking it up and look forwarding to improving cache performance with push and learn more about what’s possible under HTTP/2. Just got the After Dark theme to hit a speed index of 0.43 and am very curious how to push the boundaries further (besides, PWA, that’s coming next, and maybe alongside Fetch Injection) before cracking open a beer and putting some time into Bulma.

1 Like

I’ve been trying to find a nice way to do this, and this is what I’ve just come up with…

<style>{{ readFile "static/critical-86260bc8.css" | safeCSS }}</style>

Works pretty well. I have it hooked up to data files which I use as an asset manifest file. This method still works even with all of that funky stuff in place :slight_smile:

5 Likes

OHhhhhh YOUUURRRR the AfterDark guy… awesome.

This looks great! Yoink I’m stealing that pattern TYVM. :smiley:

How do you manage several pages?
For example, I have inlined my critical css for my homepage, but I also want to do it for all my blog posts. So, in theory, I should have a different critical css file for my blog post template.

You could use a tool like critical to render the critical CSS for each page.

I think it’s a bit worse (in terms of work required). You likely need different critical CSS files for your blog posts, because some might have an image above the fold, or a blockquote, or a code example, etc. So not every (type of) post has the same critical CSS.

If you have access to HTTP/2, it’s a better idea to use Server Push for the CSS file instead of bothering with determining critical CSS. That gives you multiple advantages over inlining CSS; see this blog post for more information: HTTP/2 for web developers.

2 Likes

Thanks, I’ll read more about that

1 Like

With Hugo 0.46 you should be able to use resources for that.

Example of SCSS

{{ with resources.Get "scss/styles.scss" | toCSS | minify }}
<style>{{ .Content | safeCSS }}</style>
{{ end }}

or CSS

{{ with resources.Get "css/styles.css" | minify }}
<style>{{ .Content | safeCSS }}</style>
{{ end }}

Note that resource files needs to be located under /assets directory (not /static)

13 Likes