Hugo Themes mit Tailwind CSS v3.x (inklusive Live-Reload)

·4 min·foundata-Team·

Wir haben im August 2023 foundata.com neu gestaltet. Wir nutzen den Static Site Generator Hugo mit einem eigens entwickeltem Theme. Als CSS-Framework kommt dabei Tailwind CSS v3 zum Einsatz.

Anstatt mit Corporate-Sprech über die Großartigkeit unseres Website-Relaunch zu langweilen 😃, will ich an dieser Stelle einige Tipps für Hugo-Theme-Entwickler teilen, die ebenfalls Tailwind CSS v3.x und dessen JIT-Compiler nutzen wollen.

Das Web ist bereits voll von nützlichen Ressourcen zu Hugo und Tailwind CSS.1 Wozu es jedoch weiterhin wenig Informationen gibt, ist die Anbindung und Nutzung von Tailwind CSS v3.x und dessen JIT-Compiler bei der Entwicklung eines Hugo-Templates. Insbesondere, wie man einen sinnvollen Entwicklungsmodus mit Live-Reload für beides baut, ist wenig bis gar nicht beschrieben. Im folgenden will ich daher darstellen, wie wir dies in unseren Hugo-Template umsetzen.2

Beispielseite und Live-Reload bei Änderungen mit hugo server

Hugo bietet einen eingebauten Web-Server. Dies bei der Template-Entwicklung so nützlich, da dieser automatisch liverelaod-js in die Seiten einbaut und diese so bei Änderungen nicht nur neu generiert, sondern der Browser ebenfalls die neuen Inhalte laden kann. Man kann so als Entwickler einfach seinen Browser offen lassen, am Template oder den Beispielinhalten eine Änderung speichern und die Seite lädt direkt neu. Ein sehr angenehmes entwickeln.

Das zu nutzende Content-Verzeichnis und das Theme können über die Parameter --source und --themesDir definiert werden. Dies ermöglicht, dass man in seinem Theme eine komplette Beispiel-Website inklusive Konfiguration verwalten kann, die alle Features des Themes nutzt und verschiedene Textlängen etc. pp. zur Optimierung der Typografie während der Entwicklung bereitstellt. Wir haben dies unter exampleSite getan.

Ich nutze den folgende Aufruf, um den Webserver mit unseren Beispielinhalten und dem Theme zu starten (eine Erklärung der einzelnen Parameter findet sich in der Dokumentation zu hugo server):

hugo server \
  --source ./exampleSite \
  --themesDir ../.. \
  --baseURL http://localhost/ \
  --port 1313 \
  --buildDrafts \
  --cleanDestinationDir \
  --logLevel debug \
  --disableFastRender \
  --gc \
  --minify \
  --noHTTPCache \
  --printI18nWarnings \
  --renderToDisk \
  --templateMetrics \
  --templateMetricsHints

Zu beachten: --logLevel debug wurde mit v0.114.0 eingeführt. Wenn die eigene Hugo-Version älter ist, muss man stattdessen --debug nutzen.

Neugenerierung der Tailwind CSS-Styles bei Änderungen via JIT-Compiler

Tailwind CSS wird üblicherweise via NodeJS installiert. Das npx tailwindcss-Kommando bietet ebenfalls eine Dateiänderungsüberwachung (--watch-Parameter) mit automatischer Neugenerierung der Styles über den JIT-Compiler.

Die CSS-Quelldatei unseres Hugo-Themes liegt unter ./assets/styles/tw-main-src.css, die Ausgabedatei wird unter ./assets/styles/tw-main-dist.css generiert. Hierzu dient das folgende Kommando:

NODE_ENV=development npx tailwindcss \
  -i ./assets/styles/tw-main-src.css \
  -o ./assets/styles/tw-main-dist.css \
  --watch --minify

Die Datei tailwind.config.js definiert, welche Verzeichnisse des Hugo-Templates nach Styles durchsucht werden sollen. In unseren Template sieht dies wie folgt aus:

//[...]
module.exports = {
  /* Content sources to tell Tailwind which files to scan for used class names for
     generating the needed CSS. More info:
     - https://tailwindcss.com/docs/content-configuration#configuring-source-paths
  */
  content: [
    "./layouts/**/*.html",
    "./content/**/*.{html,md}",
    "./themes/foundata/layouts/**/*.html",
    "./exampleSite/**/*.{html,md}",
  ],
  //[...]
}

In der Datei ./themes/foundata/layouts/_default/baseof.html sind dabei die folgende Zeilen für das Laden der so generierten CSS-Datei zuständig:

  {{/* Use a Hugo scratch to store files and content for bundling CSS and JS assets */}}
  {{ $assets := newScratch }}

  {{/* TailwindCSS */}}
  {{ $assets.Add "css" (slice (resources.Get "styles/tw-main-dist.css")) }} {{/* output file for distribution */}}

  [...]

  {{/* Get the scratch's CSS content as bundled, optimized file */}}
  {{ if $assets.Get "css" }}
    {{ $bundleCSS := $assets.Get "css" | resources.Concat "styles/bundle.css" | resources.Minify | resources.Fingerprint "sha256") }}
    <link type="text/css" rel="stylesheet" href="{{ $bundleCSS.RelPermalink }}" integrity="{{ $bundleCSS.Data.Integrity }}">
  {{ end }}

Alles zusammen nutzen via packages.json

Da NodeJS genutzt wird um die Tailwind CSS-Installation zu verwalten, liegt im root unseres Templates eine packages.json. Diese kann auch genutzt werden, um die oberen Befehle komfortabel zusammenzuführen.

Wir haben diese dazu in einzelne scripts gepackt, die via npm run <name> aufgerufen werden können. Das npm-run-all-Paket gibt uns die Möglichkeit, alles komfortabel ohne Blockaden zu verketten. In unserer packages.json sieht dies wie folgt aus:

//[...]
{
  "name": "hugo-foundata-theme",
  "version": "1.0.0",
  "description": "foundata theme for Hugo",
  "scripts": {
    [...]
    "dev": "npm-run-all --parallel dev:tailwindcss dev:example --print-name --race",
    "dev:tailwindcss": "touch ./assets/styles/tw-main-dist.css && NODE_ENV=development npx tailwindcss -i ./assets/styles/tw-main-src.css -o ./assets/styles/tw-main-dist.css --watch --minify",
    "dev:example": "hugo server --source ./exampleSite --themesDir ../.. --baseURL http://localhost/ --port 1313 --buildDrafts --cleanDestinationDir  --disableFastRender --gc --minify --noHTTPCache --printI18nWarnings --renderToDisk --templateMetrics --templateMetricsHints",
    "build:vendorlibs": "npm-run-all preinstall postinstall --print-name",
    "build:tailwindcss": "NODE_ENV=production npx tailwindcss -i ./assets/styles/tw-main-src.css -o ./assets/styles/tw-main-dist.css --minify"
  },
  //[...]
}

Das war’s. Um am Theme zu arbeiten wechselt man einfach in das Theme-Verzeichnis, führt npm run dev aus und kann unter http://localhost:1313 die Hugo-Beispielseite aufrufen. Sie wird automatisch bei jeder Änderung an einem Style, Template oder einem Beispielinhalt direkt neu generiert und dann im Browser automatisch neu geladen. Dies ist insbesondere wegen des Utility-First-Ansatzes von Tailwind CSS sehr passend.


  1. Neben der offiziellen Dokumentation fand ich insbesondere https://www.regisphilibert.com/tags/hugo/ sehr hilfreich. ↩︎

  2. Die Hugo Version v0.112.0 hat ebenfalls eine neue Option zur besseren Einbindung von Tailwind CSS v3.x eingeführt. Wir verfolgen aber einen etwas anderen Ansatz. ↩︎