Hugo dev server CSS changes not updating in browser

C:\ABOUT\WEBSITE>type hugo_c~1.htm

Hugo dev server CSS changes not updating in browser

Hugo dev server CSS changes not updating in browser

Problem

When running hugo server in development mode, edits to a theme CSS file are detected by the dev server, but the browser continues to show the old CSS. Normal reload and hard reload do not apply the change. Restarting the Hugo dev server and then reloading the page makes the CSS change visible.

Observed log example:

1
2
3
4
5
Change detected, rebuilding site (#1).
2026-06-23 13:23:58.624 +0000
Asset changed /css/theme.css
Web Server is available at http://localhost:1314/ (bind address 0.0.0.0)
Total in 26 ms

This shows that Hugo detects the asset change and rebuilds, but the served/client-visible stylesheet does not update.

Relevant template code

1
2
3
4
5
{{- $css := resources.FromString "css/theme.css" (readFile "themes/reader/assets/css/theme.css") -}}
{{- if hugo.IsProduction -}}
  {{- $css = $css | resources.Minify | resources.Fingerprint -}}
{{- end -}}
<link rel="stylesheet" href="{{ $css.RelPermalink }}"{{ if hugo.IsProduction }} integrity="{{ $css.Data.Integrity }}" crossorigin="anonymous"{{ end }}>

Diagnosis

The likely cause is the use of:

1
resources.FromString "css/theme.css" (readFile "themes/reader/assets/css/theme.css")

This creates a synthetic Hugo resource from a string. It bypasses the normal Hugo Pipes resource graph for files in assets/. As a result, Hugo may detect that the source CSS file changed, but the resource cache/live-reload chain may not invalidate the synthetic stylesheet correctly.

This explains why:

  • the dev server logs the CSS change;
  • build.cachebusters does not fix the issue;
  • browser reloads still show the old stylesheet;
  • restarting the dev server clears the stale state and makes the new CSS visible.

resources.FromString is intended for resources created from generated string content. For an actual file under assets/, use resources.Get instead.

Corrected template code

Replace the current resources.FromString + readFile pattern with a normal asset lookup:

1
2
3
4
5
6
7
{{- with resources.Get "css/theme.css" -}}
  {{- $css := . -}}
  {{- if hugo.IsProduction -}}
    {{- $css = $css | resources.Minify | resources.Fingerprint -}}
  {{- end -}}
  <link rel="stylesheet" href="{{ $css.RelPermalink }}"{{ if hugo.IsProduction }} integrity="{{ $css.Data.Integrity }}" crossorigin="anonymous"{{ end }}>
{{- end -}}

Do not include the theme directory prefix in resources.Get.

This file:

1
themes/reader/assets/css/theme.css

should be referenced as:

1
resources.Get "css/theme.css"

Theme assets are part of Hugo’s global asset lookup path.

Use this while testing the fix:

1
hugo server --disableFastRender --noHTTPCache --ignoreCache

Purpose:

  • --disableFastRender: avoids partial rebuild behavior masking template/resource changes;
  • --noHTTPCache: disables Hugo’s HTTP cache headers;
  • --ignoreCache: avoids using configured caches during the build.

These flags are useful for diagnosis, but the primary fix is replacing resources.FromString with resources.Get.

Cachebusters

The cachebuster configuration is not expected to fix the current template because the pipeline input is not a normal asset resource. It is a string generated by readFile and wrapped with resources.FromString.

A normal cachebuster rule for theme assets should match paths relative to assets/, not paths starting with themes/<theme>/assets/.

Example:

1
2
3
4
5
6
[build]
  useResourceCacheWhen = 'never'

  [[build.cachebusters]]
    source = 'assets/.*\.(scss|sass|css)$'
    target = '(css|scss|sass)'

But after switching to resources.Get, cachebusters may be unnecessary for plain CSS.

Verification

After changing the template, edit themes/reader/assets/css/theme.css, then check the served CSS directly:

1
curl -s http://localhost:1314/css/theme.css | grep 'some-new-css-token'

Interpretation:

  • If curl shows the new CSS but the browser does not, the problem is browser-side caching, live-reload CSS injection, or a service worker.
  • If curl still shows the old CSS, the problem remains in Hugo’s resource/cache pipeline.

Additional check

If the CSS include is inside a cached partial, avoid this in development:

1
{{ partialCached "..." . }}

Use this instead:

1
{{ partial "..." . }}

A cached partial can keep emitting an old resource URL or stale generated markup.

Bottom line

This is a known class of Hugo dev-server/resource-cache behavior. In this specific case, the actionable fix is to stop using resources.FromString with readFile for a real theme asset and use resources.Get "css/theme.css" instead.

Relevant references:

2026-06-23 [300f14..48e002]