Set up Hugo with Tailwind CSS in 2023

With the release of v0.112.0 , Hugo added the native support for TailwindCSS v3.x . The author of Hugo provided an example repository setting up TailwindCSS v3: bep/hugo-starter-tailwind-basic .

Note that it uses PostCSS so make sure to use Hugo extended version greater than v0.112.0.

How does it work?

According to the release note:

The basic concept is to add hugo_stats.json to the server watcher list in Hugo and trigger a new TailwindCSS build only whenever either this file or the main CSS file changes.

Add the following sections to the config.toml or hugo.toml configuration file:

[module]
  [[module.mounts]]
    source = "assets"
    target = "assets"
  [[module.mounts]]
    source = "hugo_stats.json"
    target = "assets/watching/hugo_stats.json"

[build]
  writeStats = true
  [[build.cachebusters]]
    source = "assets/watching/hugo_stats\\.json"
    target = "styles\\.css"
  [[build.cachebusters]]
    source = "(postcss|tailwind)\\.config\\.js"
    target = "css"
  [[build.cachebusters]]
    source = "assets/.*\\.(js|ts|jsx|tsx)"
    target = "js"
  [[build.cachebusters]]
    source = "assets/.*\\.(.*)$"
    target = "$1"

Also update the tailwind.config.js file to

module.exports = {
  content: [
    "./hugo_stats.json"
  ],
}

Migration

Previously, my package.json file looks like this:

{
  "scripts": {
    "dev": "NODE_ENV=development ./node_modules/tailwindcss/lib/cli.js -i ./static/tailwind.css -o ./static/main.css -w",
    "build": "NODE_ENV=production ./node_modules/tailwindcss/lib/cli.js -i ./static/tailwind.css -o ./static/main.css --minify"
  },
  "dependencies": {
    "tailwindcss": "^3.2.7",
    "@tailwindcss/typography": "^0.5.9"
  }
}

It includes separate steps:

  1. Compile Tailwind CSS file to static/main.css
  2. Build Hugo site

Sometimes, when making changes to styles, I had to manually restart the Hugo dev server to make it take effect.

After:

{
  "devDependencies": {
    "@tailwindcss/typography": "^0.5.9",
    "autoprefixer": "^10.4.14",
    "postcss": "^8.4.23",
    "postcss-cli": "^10.1.0",
    "prettier": "^2.8.8",
    "prettier-plugin-go-template": "^0.0.13",
    "tailwindcss": "^3.3.2"
  }
}

We don’t need the above dev and build command since all can be done via hugo. Win!

Create postcss.config.js:

const tailwindConfig = process.env.HUGO_FILE_TAILWIND_CONFIG_JS || "./tailwind.config.js";
const tailwind = require("tailwindcss")(tailwindConfig);
const autoprefixer = require("autoprefixer");

module.exports = {
  // eslint-disable-next-line no-process-env
  plugins: [tailwind, ...(process.env.HUGO_ENVIRONMENT === "production" ? [autoprefixer] : [])],
};

I put styles.css file under ./assets/styles. Then include it properly inside the Hugo html template file.

<!-- Styles -->
{{ $options := dict "inlineImports" true }}
{{ $styles := resources.Get "styles.css" }}
{{ $styles = $styles | resources.PostCSS }}
{{ if hugo.IsProduction }}
	{{ $styles = $styles | minify | fingerprint | resources.PostProcess }}
{{ end }}
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" />

After all these steps, the dev and build command will simply:

# dev
hugo server

# build
hugo --gc --minify

Enjoy!