skip to content
dz
3 min read

Eleventy: PostCSS und Tailwind CSS integrieren

Read this in English →

In meinem Eleventy-Tutorial habe ich gezeigt, wie man mit Eleventy einen Blog aufsetzt und wie CSS in die Seiten kommt. Hier geht es einen Schritt weiter: Tailwind CSS im selben Build.

Der verbreitete Weg ist, beides nebeneinander laufen zu lassen. Die Tailwind-CLI beobachtet das Stylesheet, Eleventy beobachtet die Templates, und in der package.json hängen zwei Skripte an npm-run-all oder concurrently. Das funktioniert zuverlässig, bedeutet aber zwei Prozesse, die dasselbe Ausgabeverzeichnis befüllen, und Eleventy weiß nichts vom fertigen Stylesheet.

Ich lasse stattdessen Eleventy das CSS selbst bauen. Ein Hook erzeugt das Stylesheet, bevor das erste Template gerendert wird, und legt den fertigen Pfad als globale Daten ab. Ein Befehl, ein Prozess, und das Layout kennt den Dateinamen.

Abhängigkeiten

pnpm add -D tailwindcss @tailwindcss/postcss postcss cssnano

@tailwindcss/postcss ist Tailwinds PostCSS-Plugin, cssnano minimiert die Ausgabe für Produktion. Das Typography-Plugin aus dem folgenden Stylesheet ist optional und braucht ein zusätzliches pnpm add -D @tailwindcss/typography.

Das Stylesheet

Tailwind v4 wird in CSS konfiguriert. Es gibt keine tailwind.config.js mehr, Plugins, Quellpfade und Design-Tokens stehen alle in der Einstiegsdatei. Bei mir liegt sie unter src/_includes/styles/tailwind.css:

@import 'tailwindcss';
@plugin '@tailwindcss/typography';

@source '../../**/*.njk';
@source '../../**/*.md';

@theme {
	--font-sans: 'Inter', sans-serif;
	--color-accent: oklch(0.62 0.19 259);
}

Die @source-Zeilen sagen dem Klassen-Scanner, wo die Templates liegen. Sie sind relativ zur CSS-Datei, ../../ landet also in src/. Tailwind kann Quellen auch selbst finden, aber sobald die Einstiegsdatei tief in src/_includes/ liegt, ist die explizite Angabe die berechenbarere Variante.

Alles unter @theme wird zu CSS-Variablen und gleichzeitig zu Utilities, --color-accent ergibt also bg-accent, text-accent und so weiter.

Der Build-Hook

Eleventy feuert einmal pro Build das eleventy.before-Event, bevor das erste Template gerendert wird. Genau der richtige Moment: Das Stylesheet liegt auf der Platte, bevor irgendeine Seite darauf verlinkt.

eleventy.config.js:

import fs from 'fs';
import path from 'path';
import { createHash } from 'crypto';
import postcss from 'postcss';
import tailwindcss from '@tailwindcss/postcss';
import cssnano from 'cssnano';

const isProduction = process.env.ELEVENTY_PRODUCTION === 'true';
const entry = 'src/_includes/styles/tailwind.css';
const outputDir = '_site';

export default function (eleventyConfig) {
	const assets = { css: '/theme.css' };

	eleventyConfig.addWatchTarget('./src/_includes/styles/');

	eleventyConfig.on('eleventy.before', async () => {
		const source = fs.readFileSync(entry, 'utf8');

		const { css } = await postcss([
			tailwindcss(),
			...(isProduction ? [cssnano({ preset: 'default' })] : [])
		]).process(source, { from: entry });

		const hash = createHash('sha256').update(css).digest('hex').slice(0, 8);
		const filename = `theme-${hash}.css`;

		fs.mkdirSync(outputDir, { recursive: true });
		fs.writeFileSync(path.join(outputDir, filename), css);

		assets.css = `/${filename}`;
	});

	eleventyConfig.addGlobalData('assets', () => assets);
}

Der Hook liest die Einstiegsdatei, schickt sie durch PostCSS und schreibt das Ergebnis ins Ausgabeverzeichnis. cssnano hängt nur in Produktion in der Plugin-Liste, im Dev-Modus spart das Zeit und die Ausgabe bleibt lesbar. Wer PostCSS ohne Tailwind fahren will, lässt tailwindcss() einfach weg.

Der Dateiname trägt die ersten acht Zeichen eines Content-Hashes. Ändert sich das CSS, ändert sich die URL, und der Browser holt sich die neue Datei, ohne dass man an Cache-Headern schrauben muss. Der Preis: Bei jedem inhaltlich neuen Build entsteht eine weitere Datei. Solange _site vor dem Build gelöscht wird, ist das kein Thema, sonst sammeln sich alte Stände an.

addGlobalData('assets', …) macht das Objekt für alle Templates verfügbar. Der Datenkaskade wird nach eleventy.before aufgebaut, der Pfad ist zu diesem Zeitpunkt also schon aktualisiert.

addWatchTarget sorgt dafür, dass Eleventy beim Speichern im Styles-Verzeichnis neu baut, solange --serve läuft. Ohne diese Zeile bemerkt der Dev-Server Änderungen am Stylesheet nicht, weil es kein Template ist.

Einbinden

Das Layout nennt die Datei nie direkt, sondern greift auf die Variable zu:

<link rel="stylesheet" href="/theme-c4283edb.css" />

Produktion

Die Minimierung hängt an einer Umgebungsvariable, die das Build-Skript setzt:

{
	"scripts": {
		"dev": "eleventy --serve",
		"build": "ELEVENTY_PRODUCTION=true eleventy"
	}
}

Wer auch unter Windows baut, nimmt cross-env dazu.


Dieser Text wurde am 9. September 2026 komplett neu geschrieben und beschreibt Eleventy 3 mit Tailwind CSS v4.

Webmentions

1 like
Sandra Parsick