Hugo detecting CSS changes but not rendering new styles

For the longest time, I lived with a problem in my Hugo site (the one you’re reading now) where after making any CSS change, I had to restart the local Hugo server—and only after running hugo serve again would it pick up the new or modified CSS.

Today, I finally figured out why this was happening, and I fixed it.

Solution

To save you time if you’re not interested in the steps I took to debug this, here’s the solution.

If you have CSS files in the static/ directory, move them to assets/, particularly if you are using an asset pipeline such as piping the CSS file to fingerprint for cache busting.

The original problem

After changing any CSS, despite Hugo outputting that it synced the CSS file in the console, the page still had the old styles.

Change of Static files detected, rebuilding site (#2).
2024-04-28 13:40:19.987 -0500
Syncing /style.out.css to /

The CSS file was outdated, cached, or something, because it didn’t have the latest styles.

Environment

Context is important when debugging. Here’s what my setup looked like.

Debugging

These are the steps I took, in the same order I tried them.

Is it a browser bug?

I use Firefox, which has a decreasing market share and, in some ways, is behind the other browsers.

To make sure it’s not a Firefox bug, I tried editing some CSS and seeing the result in both Chrome and Safari, but it was the same. I changed the CSS, but the page did not have the new styles.

What does the requested CSS file look like?

Since I’m using Tailwind, for context I want to mention that any new or edited CSS is actually me adding or removing a class in HTML, which Tailwind detects and then generates an output style.out.css file, which then Hugo detects as changed and does its thing.

I opened the network tab, edited some CSS, and observed what the CSS file looked like when it was requested. The hash in the filename remained the same. This file which Hugo generated was not updating, despite Hugo saying that it synced. Maybe syncing is not what I think it is?

From the browser dev tools, I confirmed the class I added, which appeared in the HTML, did not appear in the CSS.

Is something wrong with Tailwind CSS?

I wanted to confirm which side of the build pipeline had a problem. Was it Hugo or Tailwind? It’s like a binary search to narrow down where the problem is coming from.

I went to the directory where Tailwind outputs its file, which is in the same directory as the source CSS file. My source file is style.css, and after Tailwind runs, the output is style.out.css.

The output file had the new CSS class I added! Therefore, Tailwind CSS is working as it should.

There’s something wrong on the Hugo side

After eliminating Tailwind from causing the problem, I focused on the other side of the build pipeline, where Hugo takes over.

I looked inside the public/ directory where the final static site is built. I noticed three CSS files: style.css, style.out.css, and finally style.out.8b98b71679a56b90834339929b690ec508970a1655e6527d81c1c64a7fca8f68.css. A little messy on my part, but having the source files here shouldn’t hurt anything, as the HTML only references the fingerprinted file.

I confirmed again that the fingerprinted version does not have the class I added. However, I could now check what’s in style.out.css to see if Hugo copied the file here correctly. This file did have the correct classes.

Maybe it’s the fingerprinting?

Hugo adds a hash to a filename when the file is piped through fingerprint.

{{ $style := resources.Get "style.out.css" | fingerprint }}

In my template, I use the result in

<link rel="stylesheet" href="{{ $style.RelPermalink }}">

I took out fingerprint from the pipeline, did my experiment again, and discovered that the problem was no longer there. The browser was loading style.out.css which had the correct CSS.

So, what’s up with fingerprint?

I put fingerprint back in the pipeline and found something interesting while inspecting the fingerprinted file.

I noticed it had class names I previously experimented with but that were no longer in style.out.css.

The fingerprinted file could not have been created from style.out.css, as I had deleted the public/ directory before restarting Hugo. Where did the fingerprinted file come from?

Cache, go away!

I found one solution I thought might work. I tried running hugo serve --noHTTPCache --ignoreCache --disableFastRender which appears to ignore all caches, as that might have explained why the fingerprinted file had old class names in it.

However, the problem persisted.

Where do I go from here?

At this point, the interesting thing to me is how Hugo can output

Change of Static files detected, rebuilding site (#2).
2024-04-28 14:30:02.402 -0500
Syncing /style.out.css to /

which indicates it synced the file, yet—the fingerprinted file is not coming from this file.

It indeed can’t be the same file that’s piped through fingerprint.

Something must be wrong with my understanding of Hugo

I went back to reading about asset pipelines and fingerprinting in Hugo from other blogs I found in Google, hoping to understand more.

I searched the Hugo forums to get insight about fingerprinting. Other people had similar problems, but they were due to other causes like editing a comment in a CSS file that gets removed through minify before it gets piped to fingerprint, resulting in an identical file hash.

In Google I saw my own blog post from 5 years ago about cache busting in Hugo using the same method I have now. Full circle, huh.

The clue I needed

I clicked one result from a personal blog. The post was titled Cache Busting CSS (and stuff) in Hugo, and in the footnotes, I found the clue I needed.

Stuff I got wrong

When doing this simple bit, was that I had my CSS in the static/ folder. In order to Get resources, they must be in the assets/ folder. Bam. Fixed.

I read the Hugo docs on directory structure, and indeed, assets/ is where CSS is supposed to go, a change introduced in a prior version of Hugo.

I moved the files, updated paths, and removed a config line, assetDir = 'static', that I added after a Hugo update broke my site. Fixed. It all works perfectly.

As a bonus, those extra CSS files no longer appear in the output of the Hugo build. There’s only the final, fingerprinted CSS file.

I should have known something was up when I added that config line.