Support - TailwindCSS - HTML Template change not triggering CSS/PostCSS rebuild

I’m creating a theme using TailwindCSS, PostCSS and PrelineUI.

Currently, I’m noticing some unexpected behavior when making changes to my baseof.html template.

After applying changes, hugo correctly updates hugo_stats.json, but fails to rebuild main.scss, resulting in TailwindCSS not being “invoked” by the PostCSS pipe.

This however doesn’t happen when changing site configuration, and PostCSS gets correctly invoked on rebuild

Logs - Config file changed

Start building sites …
hugo v0.125.4-cc3574ef4f41fccbe88d9443ed066eb10867ada2+extended windows/amd64 BuildDate=2024-04-25T13:27:26Z VendorInfo=gohugoio

INFO  static: removing all files from destination that don't exist in static dirs
INFO  static: syncing static files to \ duration 24.7753ms
INFO  build:  step process substep collect files 1 files_total 1 duration 512.8µs
INFO  build:  step process duration 1.0396ms
INFO  build:  step assemble duration 516.7µs
INFO  postcss: use config file "<redacted>\\config\\postcss.config.js"
INFO  build:  step render substep pages site en outputFormat html duration 2.2194972s
INFO  build:  step render pages 8 content 7 duration 2.2206596s
INFO  build:  step postProcess duration 513.4µs
INFO  build:  duration 2.2241057s

                   | EN
-------------------+-----
  Pages            |  8
  Paginator pages  |  0
  Non-page files   |  0
  Static files     | 13
  Processed images |  0
  Aliases          |  0
  Cleaned          |  0

Built in 2258 ms
Environment: "development"
Serving pages from disk
Web Server is available at //localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

Change of config file detected, rebuilding site (#1).
2024-05-03 20:28:51.918 +0200
INFO  static: removing all files from destination that don't exist in static dirs
INFO  static: syncing static files to \ duration 23.0219ms
INFO  build:  step process substep collect files 1 files_total 1 duration 1.0024ms
INFO  build:  step process duration 1.0024ms
INFO  build:  step assemble duration 0s
INFO  postcss: use config file "<redacted>\\config\\postcss.config.js"
INFO  build:  step render substep pages site en outputFormat html duration 1.9323102s
INFO  build:  step render pages 8 content 7 duration 1.9338331s
INFO  build:  step postProcess duration 527µs
INFO  build:  duration 1.9369183s
Rebuilt in 1971 ms

Logs - baseof.html template changed

Change detected, rebuilding site (#2).
2024-05-03 20:32:04.410 +0200
DEBUG cachebuster: Matching "layouts/_default/baseof.html" with source "assets/hugo/hugo_stats\\.json": no match
DEBUG cachebuster: Matching "layouts/_default/baseof.html" with source "config/(postcss|tailwind)\\.config\\.js": no match
DEBUG cachebuster: Matching "layouts/_default/baseof.html" with source "assets/.*\\.(.*)$": no match
Template changed /_default/baseof.html
DEBUG Direct dependencies of "_default/baseof.html" (identity.StringIdentity) =>
INFO  build:  step process substep resolve page output change set changes 1 checked 16 matches 7 duration 0s
INFO  build:  step process substep gc dynacache duration 0s
INFO  build:  step process substep rebuild templates duration 10.7905ms
Web Server is available at //localhost:1313/ (bind address 127.0.0.1)
INFO  build:  step process duration 14.8778ms
INFO  build:  step assemble duration 0s
INFO  build:  step render substep pages site en outputFormat html duration 8.5276ms
INFO  build:  step render pages 7 content 7 duration 9.7562ms
INFO  build:  step postProcess duration 0s
INFO  build:  duration 27.2723ms
Total in 27 ms
DEBUG Received System Events: [WRITE         "<redacted>\\hugo_stats.json" WRITE         "<redacted>\\hugo_stats.json"]

Change detected, rebuilding site (#3).
2024-05-03 20:32:04.905 +0200
DEBUG cachebuster: Matching "assets/hugo/hugo_stats.json" with source "assets/hugo/hugo_stats\\.json": match!
DEBUG cachebuster: Matching "assets/hugo/hugo_stats.json" with source "config/(postcss|tailwind)\\.config\\.js": no match
DEBUG cachebuster: Matching "assets/hugo/hugo_stats.json" with source "assets/.*\\.(.*)$": match!
Asset changed /hugo/hugo_stats.json
DEBUG Direct dependencies of "/hugo/hugo_stats.json" (*paths.Path) =>
DEBUG Matching "/get-resources-for-page" with target "(css|scss)": no match
DEBUG Matching "/get-resources-for-page" with target "json": no match
DEBUG Matching "/categories/get-resources-for-page" with target "(css|scss)": no match
DEBUG Matching "/categories/get-resources-for-page" with target "json": no match
DEBUG Matching "/categories/component/get-resources-for-page" with target "(css|scss)": no match
DEBUG Matching "<redacted>/.implementation-sample/content/index.md/html" with target "(css|scss)": no match
DEBUG Matching "scss/main.scss_a1f0c10221d7b9f927e91d5befe78bdb" with target "(css|scss)": match!
DEBUG Matching "/scss/main.scss__get" with target "(css|scss)": match!
DEBUG Matching "<redacted>/.implementation-sample/content/index.md/html" with target "(css|scss)": no match
DEBUG Matching "13" with target "(css|scss)": no match
DEBUG Matching "/categories/component/get-resources-for-page" with target "json": no match
DEBUG Matching "<redacted>/.implementation-sample/content/index.md/html" with target "json": no match
DEBUG Matching "scss/main.scss_a1f0c10221d7b9f927e91d5befe78bdb" with target "json": no match
DEBUG Matching "/scss/main.scss__get" with target "json": no match
DEBUG Matching "<redacted>/.implementation-sample/content/index.md/html" with target "json": no match
DEBUG Matching "16/html" with target "(css|scss)": no match
DEBUG Matching "16/html" with target "json": no match
DEBUG Matching "13" with target "json": no match
DEBUG Matching "/tags/get-resources-for-page" with target "(css|scss)": no match
DEBUG Matching "16/html" with target "(css|scss)": no match
DEBUG Matching "/external-deps/preline/preline.js__get" with target "(css|scss)": no match
DEBUG Matching "11/html" with target "(css|scss)": no match
DEBUG Matching "11/html" with target "json": no match
DEBUG Matching "15/html" with target "(css|scss)": no match
DEBUG Matching "15/html" with target "json": no match
DEBUG Matching "14" with target "(css|scss)": no match
DEBUG Matching "/tags/get-resources-for-page" with target "json": no match
DEBUG Matching "16/html" with target "json": no match
DEBUG Matching "11/html" with target "(css|scss)": no match
DEBUG Matching "/external-deps/preline/preline.js__get" with target "json": no match
DEBUG Matching "12/html" with target "(css|scss)": no match
DEBUG Matching "12/html" with target "json": no match
DEBUG Matching "14" with target "json": no match
DEBUG Matching "/categories/circuit/get-resources-for-page" with target "(css|scss)": no match
DEBUG Matching "11/html" with target "json": no match
DEBUG Matching "15/html" with target "(css|scss)": no match
DEBUG Matching "15/html" with target "json": no match
DEBUG Matching "12/html" with target "(css|scss)": no match
DEBUG Matching "12/html" with target "json": no match
DEBUG Matching "17/html" with target "(css|scss)": no match
DEBUG Matching "17/html" with target "(css|scss)": no match
DEBUG Matching "<redacted>/.implementation-sample/content/index.md" with target "(css|scss)": no match
DEBUG Matching "/categories/circuit/get-resources-for-page" with target "json": no match
DEBUG Matching "17/html" with target "json": no match
DEBUG Matching "18/html" with target "(css|scss)": no match
DEBUG Matching "18/html" with target "json": no match
DEBUG Matching "17/html" with target "json": no match
DEBUG Matching "18/html" with target "(css|scss)": no match
DEBUG Matching "18/html" with target "json": no match
DEBUG Matching "<redacted>/.implementation-sample/content/index.md" with target "json": no match
DEBUG Matching "16" with target "(css|scss)": no match
DEBUG Matching "/tags/documentation/get-resources-for-page" with target "(css|scss)": no match
DEBUG Matching "/tags/documentation/get-resources-for-page" with target "json": no match
DEBUG Matching "/tags/guide/get-resources-for-page" with target "(css|scss)": no match
DEBUG Matching "/tags/guide/get-resources-for-page" with target "json": no match
DEBUG Matching "16" with target "json": no match
DEBUG Matching "11" with target "(css|scss)": no match
DEBUG Matching "11" with target "json": no match
DEBUG Matching "15" with target "(css|scss)": no match
DEBUG Matching "15" with target "json": no match
DEBUG Matching "12" with target "(css|scss)": no match
DEBUG Matching "12" with target "json": no match
DEBUG Matching "17" with target "(css|scss)": no match
DEBUG Matching "17" with target "json": no match
DEBUG Matching "18" with target "(css|scss)": no match
DEBUG Matching "18" with target "json": no match
INFO  build:  step process substep gc dynacache cachebuster duration 9.9549ms
INFO  build:  step process substep resolve page output change set changes 1 checked 16 matches 0 duration 0s
INFO  build:  step process substep gc dynacache duration 0s
Web Server is available at //localhost:1313/ (bind address 127.0.0.1)
INFO  build:  step process duration 13.3093ms
INFO  build:  step assemble duration 0s
INFO  build:  step render substep pages site en outputFormat html duration 0s
INFO  build:  step render pages 0 content 0 duration 0s
INFO  build:  step postProcess duration 0s
INFO  build:  duration 14.8676ms
Total in 15 ms

My understanding is that tailwind should “see” the changes happening to hugo_stats.json, after such operation, as suggested on the hugo-starter-tailwind-basic repository

Environment

hugo v0.125.4-cc3574ef4f41fccbe88d9443ed066eb10867ada2+extended windows/amd64 BuildDate=2024-04-25T13:27:26Z VendorInfo=gohugoio
GOOS="windows"
GOARCH="amd64"
GOVERSION="go1.22.2"
github.com/sass/libsass="3.6.5"        
github.com/webmproject/libwebp="v1.3.2"

Work Tree

├───.implementation-sample
│   │   hugo.yaml
│   │
│   ├───archetype
│   │       default.md
│   │
│   └───content
│           index.md
│
├───archetypes
│       default.md
│
├───assets
│   ├───css
│   ├───js
│   │       main.js
│   │
│   └───scss
│           main.scss
│
├───config
│   │   postcss.config.js
│   │   tailwind.config.js
│   │
│   ├───development
│   │       build.yaml
│   │       server.yaml
│   │
│   └───_default
│           build.yaml
│           hugo.yaml
│           module.yaml
│           params.yaml
│
├───layouts
│   ├───partials
│   │   │   head.html
│   │   │
│   │   ├───footer
│   │   └───head
│   │           site-header.html
│   │           site-metadata.html
│   │           site-scripts.html
│   │           site-stylesheets.html
│   │
│   ├───shortcodes
│   └───_default
│           baseof.html
│           list.html
│           single.html
│
└───static
    └───fonts
        └───Roboto
                LICENSE.txt
                Roboto-Black.ttf
                Roboto-BlackItalic.ttf
                Roboto-Bold.ttf
                Roboto-BoldItalic.ttf
                Roboto-Italic.ttf
                Roboto-Light.ttf
                Roboto-LightItalic.ttf
                Roboto-Medium.ttf
                Roboto-MediumItalic.ttf
                Roboto-Regular.ttf
                Roboto-Thin.ttf
                Roboto-ThinItalic.ttf

config/_default/module.yaml

mounts:
  - source: "hugo_stats.json"
    target: "assets/hugo/hugo_stats.json"

  # Mount external dependencies (node_modules)
  - source: "node_modules/tailwindcss"
    target: "assets/external-deps/tailwindcss"

  - source: "node_modules/preline/dist"
    target: "assets/external-deps/preline"

  - source: "node_modules/preline/preline.js"
    target: "assets/external-deps/preline/preline.js"

  # We don't have any content on the theme itself, mount it from .implementation-sample instead (for now)
  - source: ".implementation-sample/content"
    target: "content"

  - source: "assets"
    target: "assets"

config/development/build.yaml

noJSConfigInAssets: false

buildStats:
  enable: true

cachebusters:
  - source: "assets/hugo/hugo_stats\\.json"
    target: "(css|scss)"

  - source: "config/(postcss|tailwind)\\.config\\.js"
    target: "(css|scss)"

  - source: "assets/.*\\.(.*)$"
    target: "$1"

config/tailwind.config.js

/**
 * @type {import('tailwindcss').Config}
 * */
module.exports = {
	content: ["node_modules/preline/dist/*.js", "hugo_stats.json"],
	plugins: ["preline/plugin", "typography"],
	theme: {},
};

config/postcss.config.js

module.exports = {
	plugins: {
		"postcss-import": {},
		"tailwindcss": {
			config: "config/tailwind.config.js",
		},
		...(process.env.HUGO_ENVIRONMENT === "production" && {
			autoprefixer: {},
		}),
	},
};

layouts/_default/baseof.html

<!doctype html>
<html
    lang="{{ default `en-US` site.Language.LanguageCode}}"
    dir="{{ default `ltr` site.Language.LanguageDirection }}">
    <head>
        {{ partial "head.html" . }}
    </head>
    <body class="bg-black py-20 text-indigo-300">
        {{- .Content -}}
    </body>
</html>

layouts/partials/head.html

{{ partial "head/site-metadata.html" . }}
{{ partial "head/site-stylesheets.html" . }}
{{ partial "head/site-scripts.html" . }}

<title>{{- default .Title .Site.Title -}}</title>
{{ partial "head/site-header.html" . }}

layouts/partials/head/site-stylesheets.html

{{ $_postcss_options := dict 
    "config" "config/postcss.config.js" 
    "noMap" (hugo.IsProduction)
}}
{{ $_transpiler_options := dict
    "targetPath" "css/bundle.css"
    "enableSourceMap" (not hugo.IsProduction)
}}
{{ $stylesheet := resources.Get "scss/main.scss" | toCSS $_transpiler_options | postCSS $_postcss_options }}
{{ if hugo.IsDevelopment }}
    <link
        rel="stylesheet"
        href="{{- $stylesheet.RelPermalink -}}" />
{{ else }}
    {{ $stylesheet :=  $stylesheet | minify | fingerprint "sha512" | resources.PostProcess }}
    <link
        rel="stylesheet"
        href="{{- $stylesheet.RelPermalink -}}"
        integrity="{{- $stylesheet.Data.Integrity -}}" />
{{ end }}

assets/scss/main.scss

@tailwind base;
@tailwind variants;
@tailwind utilities;
@tailwind components;

hugo_stats.json

{
  "htmlElements": {
    "tags": [
      "a",
      "body",
      "em",
      "h1",
      "head",
      "hr",
      "html",
      "li",
      "link",
      "meta",
      "p",
      "script",
      "strong",
      "title",
      "ul"
    ],
    "classes": [
      "bg-black",
      "py-20",
      "text-indigo-300",
      "text-teal-300"
    ],
    "ids": null
  }
}

Update

Upon further inspection, and some more reading on similar discussions in the forum, something very similar seems to have been reported in the past, prior to hugo_stats.json and cachebusters implementation.

I am wondering if tailwind’s @tailwind directive could have something to do with all of this (in main.scss)

I’ve tried using SASS’s @import directive, importing tailwind from assets/external-deps/tailwindcss/tailwind after mounting node_modules/tailwindcss to assets/external-deps/tailwindcss/ through the hugo union filesystem, but in my scenario, libsass fails to resolve directories in the union fs.

@bep sorry for the direct mention, pinging you since it’s related to your tailwindcss basic implementation.

Hopefully this is just an oversight on my end and not a hugo issue.

Thanks in advance for your assistance :slightly_smiling_face:

I haven’t read this entire thread, but I see lots of “mounting into node_modules” etc., which does not come from “my” tailwindcss basic implementation.

I just tested the below OK on my MacBook:

  1. Clone: git@github.com:bep/hugo-starter-tailwind-basic.git
  2. cd hugo-starter-tailwind-basic
  3. npm install
  4. hugo server
  5. Edit Tailwind class in HTML => browser refreshes OK
  6. Edit CSS file => browser refreshes OK.

I suggest you start by doing the same and report back the results.

Apologies for the late reply.

Indeed, the hugo-starter-tailwind-basic repository does not specifically mention “mounting into node_modules”, however, the union fs mounting part is irrelevant, since the problem is related to the way hugo builds/rebuilds SCSS resources.

Hugo manages to successfully build everything on execution, however it fails to refresh the CSS resources when updating .html files specifically.

As you can probably see in the 2 different logs I presented.

Config file changed
HTML Template changed

Thank you for your time

Hey @AncientDF, you can try Hugoplate, it’s a Hugo starter template built with tailwindcss, it should work as expected. thanks

Sure, but you mentioned my name and my Tailwind started, so I assumed you meant that that particular project didn’t work. It’s hard for me to debug some unknown setup.

Hello, thank you for the example.

I have already looked into Hugoplate and it’s Tailwind implementation. Unfortunately the theme itself is too heavy for my use-case and thus I opted for a custom theme instead.

Apologies for the confusion. I think my post could’ve been organized better, avoiding unnecessary clutter. I was trying to describe my entire work environment to provide as much context as possible.

Regarding the issue. I updated to the latest Hugo release and the problem still persists. While trying to debug this further, I also noticed changes to tailwind.config.js/postcss.config.js do not trigger a site rebuild.

At this point, I am positive something is wrong with my setup and this is not a Hugo issue.

This is what I see on hugo startup

Watching for changes in <redacted>\{.implementation-sample,archetypes,assets,hugo_stats.json,layouts,node_modules,package.json,static}
Watching for config changes in <redacted>\config\_default, <redacted>\config\development, <redacted>\go.mod

Mounts:

mounts:
  - source: "hugo_stats.json"
    target: "assets/hugo/hugo_stats.json"

  # Mount external dependencies (node_modules)
  - source: "node_modules/tailwindcss"
    target: "assets/external-deps/tailwindcss"

  - source: "node_modules/preline/dist"
    target: "assets/external-deps/preline"

  - source: "node_modules/preline/preline.js"
    target: "assets/external-deps/preline/preline.js"

  # We don't have any content on the theme itself, mount it from .implementation-sample instead (for now)
  - source: ".implementation-sample/content"
    target: "content"

  - source: "assets"
    target: "assets"

Build

noJSConfigInAssets: false

buildStats:
  enable: true

cachebusters:
  # Bust stylesheets after site changes (Complement TailwindCSS JIT)
  - source: "assets/hugo/hugo_stats\\.json"
    target: "css"

  # Bust stylesheets after config changes (Complement TailwindCSS JIT)
  - source: "config/(postcss|tailwind)\\.config\\.js"
    target: "css"

Thank you once again for your time.

Related: Caching issues with hugo server - #10 by edwardh

Bump

Update

Upon further investigation, the real culprit seems to be the underlying unionfs system.

On some occasions, the following statement seems to silently fail:

mounts:
  - source: "assets"
    target: "assets"

  # TailwindCSS v3.0 JIT compile functionality
  - source: "hugo_stats.json"
    target: "assets/hugo/hugo_stats.json"

Even though the hugo_stats.json file is successfully updated, hugo fails to see the changes.
I’ve managed to make it work semi consistently on “first start” by simply triggering a rebuild by modifying one of the .yaml config files, as a workaround

Update

Issue reappeared. Now, although hugo seemingly “rebuilds” upon hugo_stats file changes, the tailwind removed/added classes do not show up unless a full site rebuild takes place.

Judging from the lack of real feedback (considering this issue has been reported for quite some time by now), I’m going to assume this does not fall into the list of development priorities.

Any suggestions on how to proceed? I am considering switching real-time development to tailwind’s general liveserver/npm watch approach, but would love to avoid that as much as possible

I’m not sure if that works with the postcss stuff, but wouldn`t actively polling a workaround?

hugo server --poll 500ms

Just tried it, unfortunately same results.

I am unsure if the 2 issues are related at this point (race condition vs my issue)

The number of times I have had to start and stop the server when building a site today with Tailwind are well above 50 by now. I also assume the issue is on low priority.

@Arif @AncientDF

If you want someone to spend any time looking into either or your issues, I suggest you create and share a minimal reproducible example including the manual steps required to trigger the problem.

A minimal reproducible example is not multiple paragraphs of explanation with multiple code snippets and multiple logs. A minimal reproducible example is a very small project, created specifically to reproduce the problem, that we can git clone.

The odds of someone digging into a complex project/module/theme are close to zero; we have neither the time nor the inclination. You need to help us to help you.

As a general note here:

I’m close to wrap up a new feature in Hugo that’s a new defer keyword in the templates, which allows you to defer a template block to be executed after all the rendering. I’m wrapping my head around some “live reload” technical issues as I write this, but my initial tests shows that the “Tailwind workflow” can be simplified, mostly in the sense that it should not be needed to have the hugo_stats.json file watched anymore, and shoud also give smoother rebuilds when you add new Tailwind classes to your templates (today that triggers 2 builds, 1 for the template change and then 1 for the CSS change).

That said, I have used Tailwind 3.x with Hugo extensively myself for the last few years, and it’s been working great.

I think my issue is related to this. I think as a project grows, the issue becomes more prominent. So, I guess I will have to dedicate time to strip down the site (and we are talking tens of hours) and test one by one.

(I have diagnosed everything, from cache busters, to disabling fast render…but the issue still happens. So, I would not know what to point at as the culprit).

EDIT: Hugo v0.128.0 addresses this issue with defer.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.