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.