CI/CD
.yml
GitLab · npm · Tailwind CSS · Hyvä · Frontend-Build
npm, Tailwind und Frontend-Builds
in GitLab Pipelines integrieren

Hyvä Themes bringt Tailwind CSS v4 als CSS-first-Build in Magento. Wer diesen Build in die GitLab-Pipeline integriert, muss Node.js-Versionen, npm-Caching, Artefakt-Übergabe und die Reihenfolge gegenüber dem PHP-Build sauber koordinieren. Dieser Artikel zeigt, wie das in der Praxis funktioniert.

13 Min. Lesezeit npm ci · Tailwind v4 · node_modules Cache · Artefakt Magento 2.4 · Hyvä · GitLab CI/CD · Node.js 20

1. Warum der Frontend-Build in die Pipeline gehört

Der häufigste Fehler bei Magento-Projekten mit Hyvä Themes ist ein Frontend-Build, der auf dem Entwickler-Laptop läuft und dessen Ergebnisse ins Repository committed werden. Das klingt pragmatisch, ist aber in der Praxis eine kontinuierliche Quelle von Inkonsistenzen: unterschiedliche Node.js-Versionen, unterschiedliche npm-Versionen, unterschiedliche Tailwind-Konfigurationen auf verschiedenen Entwickler-Maschinen führen zu CSS-Dateien, die je nach Umgebung leicht abweichen. Wer das Ergebnis committet, versioniert Artefakte statt Quellcode.

Die Alternative ist ein reproduzierbarer Build in der GitLab-Pipeline. Dort ist die Node.js-Version fixiert, npm ci stellt sicher, dass exakt die Versionen aus package-lock.json installiert werden, und das CSS-Build-Ergebnis ist ein deterministisches Artefakt des Pipeline-Runs. Das Ergebnis liegt dann nicht im Repository, sondern wird als Pipeline-Artefakt zwischen den Jobs übergeben und am Ende auf den Server deployed. Das ist sauberer, reproduzierbarer und schützt das Repository vor generiertem Code.

2. Node.js-Version festlegen und reproduzieren

Die Node.js-Version ist der häufigste Konfigurations-Drift zwischen Entwickler-Umgebung und CI. In Hyvä-Projekten mit Tailwind CSS v4 sollte Node.js 20 (LTS) verwendet werden. Die Version wird in der Datei .nvmrc im Repository festgelegt – ein einzeiliges File mit dem Inhalt 20. GitLab CI verwendet dann das Docker-Image node:20-alpine für den Frontend-Build-Job. So ist die Version sowohl lokal (via nvm) als auch in der Pipeline identisch.

Ein weiterer Stolperstein ist npm selbst. npm install und npm ci haben unterschiedliches Verhalten: npm install aktualisiert package-lock.json bei Bedarf, npm ci schlägt fehl wenn package.json und package-lock.json nicht übereinstimmen. In der CI-Pipeline gehört ausschließlich npm ci – das ist schneller, deterministisch und signalisiert sofort, wenn ein Entwickler package-lock.json vergessen hat zu committen. Das ist kein Komfort-Feature, sondern eine Reproduzierbarkeits-Garantie.

# .gitlab-ci.yml — Frontend build job for Hyvä / Tailwind CSS v4
build:frontend:
  stage: build
  image: node:20-alpine
  variables:
    # Cache npm modules across pipeline runs
    NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.cache/npm"
    THEME_PATH: "app/design/frontend/Mironsoft/default"
  cache:
    key:
      files:
        # Cache key changes when lock file changes
        - "${THEME_PATH}/web/tailwind/package-lock.json"
    paths:
      - "${THEME_PATH}/web/tailwind/node_modules/"
      - .cache/npm/
    policy: pull-push
  script:
    # Install exact versions from lock file (no updates)
    - npm ci
        --prefix "${THEME_PATH}/web/tailwind"
        --prefer-offline
    # Build Tailwind CSS v4 (CSS-first approach)
    - npm run build
        --prefix "${THEME_PATH}/web/tailwind"
  artifacts:
    name: "frontend-${CI_COMMIT_SHORT_SHA}"
    paths:
      # Only the compiled CSS, not node_modules
      - "${THEME_PATH}/web/css/"
    expire_in: 1 day
    when: on_success

3. npm-Caching in GitLab CI korrekt konfigurieren

Der größte Zeitfresser im Frontend-Build-Job ist das Herunterladen von npm-Paketen. Mit einem korrekt konfigurierten GitLab-Cache für node_modules fällt dieser Schritt bei unveränderten Abhängigkeiten komplett weg. Der Cache-Key muss auf package-lock.json zeigen: wenn sich die Lock-Datei ändert, wird ein neuer Cache erstellt. Wenn sie unverändert ist, wird der vorhandene Cache verwendet und npm ci mit --prefer-offline ausgeführt, was das lokale Cache-Verzeichnis bevorzugt.

Wichtig ist die Unterscheidung zwischen GitLab-Cache und GitLab-Artefakt. node_modules gehört in den Cache: es ist groß, wird zwischen Pipeline-Runs wiederverwendet und muss nicht versioniert werden. Das kompilierte CSS gehört in das Artefakt: es ist klein, wird zwischen Jobs innerhalb eines Pipeline-Runs weitergegeben und muss genau reproduzierbar sein. Wer node_modules als Artefakt definiert, lädt Hunderte Megabyte unnötig zwischen Jobs. Wer das kompilierte CSS nur im Cache hat, riskiert fehlende Artefakte beim nächsten Job.

4. Tailwind CSS v4 im Pipeline-Job kompilieren

Tailwind CSS v4 verwendet einen CSS-first-Ansatz: anstatt einer tailwind.config.js steuert eine theme.css-Datei die Konfiguration. Das Build-Kommando in Hyvä-Projekten lautet typischerweise npm run build, das intern Tailwind CLI mit dem konfigurierten Input-File aufruft. In der GitLab-Pipeline muss sichergestellt sein, dass das Input-File korrekt referenziert ist und dass der Output-Pfad mit dem erwarteten Artefakt-Pfad übereinstimmt.

Ein häufiges Problem: Tailwind v4 scannt standardmäßig alle Template-Dateien im Projekt nach verwendeten CSS-Klassen. In einer CI-Umgebung ist der Scan-Pfad wichtig: er muss die .phtml-Dateien des Hyvä-Themes erfassen, aber nicht den vendor/-Ordner durchsuchen, der Hunderte Megabyte an Dateien enthalten kann. Die Tailwind-Konfiguration in theme.css mit @source-Direktiven auf die relevanten Template-Verzeichnisse zu begrenzen, halbiert die Build-Zeit in vielen Projekten.

5. Build-Artefakt sauber übergeben

In einer mehrstufigen GitLab-Pipeline muss das Frontend-Build-Artefakt vom Build-Job an den Deploy-Job weitergegeben werden. Das geschieht über GitLab-Artefakte mit artifacts.paths. Der Build-Job definiert, welche Pfade als Artefakt gespeichert werden, und der Deploy-Job kann über needs auf diese Artefakte zugreifen. Wichtig: Artefakte werden automatisch zwischen Jobs in derselben Pipeline weitergegeben – auch wenn sie in unterschiedlichen Stages laufen.

Die kritische Entscheidung ist, welche Dateien Teil des Artefakts sein sollen. Für den Frontend-Build sind das: die kompilierte CSS-Datei unter web/css/styles.css und optionale Source-Maps für das Debugging. Nicht im Artefakt: node_modules/, das Build-Tool selbst, temporäre Dateien. Ein schlankes Artefakt beschleunigt den Upload und Download zwischen Jobs erheblich und reduziert den GitLab-Artefakt-Speicher. Eine typische kompilierte Tailwind-CSS-Datei ist 50–150 KB – das Artefakt sollte nie größer als wenige Megabyte sein.

6. Static Content Deploy nach dem Frontend-Build

In Magento mit Hyvä ist der Static Content Deploy (SCD) ein separater Schritt, der nach dem Frontend-Build läuft. SCD kompiliert Magento-Module-spezifische Assets, verarbeitet require-js-Bundles (in Hyvä minimal) und kopiert Theme-Assets in den pub/static/-Ordner. In der GitLab-Pipeline läuft SCD entweder als Teil des Build-Jobs (was Magento als Abhängigkeit erfordert) oder auf dem Zielserver als Teil des Deploy-Jobs.

Die sauberere Lösung für Hyvä-Projekte: SCD im Build-Job mit dem PHP-Image ausführen, nachdem der Frontend-Build das CSS produziert hat. Das Ergebnis – der komplette pub/static/-Ordner – wird als Artefakt weitergegeben und auf den Server deployed, ohne dass auf dem Server ein einzelner SCD-Befehl ausgeführt werden muss. Das ist schneller, reproduzierbarer und vermeidet das Problem, dass der Server während des SCD keine statischen Assets liefern kann.

# Combined PHP + Frontend build job
build:magento:
  stage: build
  image: php:8.4-cli
  variables:
    COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
    THEME_PATH: "app/design/frontend/Mironsoft/default"
  cache:
    key:
      files:
        - composer.lock
    paths:
      - .cache/composer/
    policy: pull-push
  before_script:
    # Install Node.js 20 into PHP image
    - curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
    - apt-get install -y nodejs
    # Install PHP extensions required by Magento
    - docker-php-ext-install pdo_mysql bcmath intl
  script:
    # PHP dependencies
    - composer install
        --no-dev --prefer-dist --no-interaction --optimize-autoloader
    # Frontend build (npm ci + Tailwind)
    - npm ci --prefix "${THEME_PATH}/web/tailwind" --prefer-offline
    - npm run build --prefix "${THEME_PATH}/web/tailwind"
    # Magento DI compile
    - php bin/magento setup:di:compile
    # Static content deploy for all locales
    - php bin/magento setup:static-content:deploy
        de_DE en_US
        -t Mironsoft/default --force
  artifacts:
    name: "magento-build-${CI_COMMIT_SHORT_SHA}"
    paths:
      - vendor/
      - generated/
      - pub/static/
    expire_in: 2 hours

7. Typische Fehler beim Frontend-Build in CI

Der häufigste Fehler ist eine fehlende oder veraltete package-lock.json. Wenn npm ci in der Pipeline fehlschlägt, liegt es fast immer daran, dass die Lock-Datei nicht dem aktuellen Stand von package.json entspricht. Die Lösung: lokal npm install ausführen, die aktualisierte Lock-Datei committen und die Pipeline neu starten. npm ci darf niemals still die Lock-Datei aktualisieren – das ist sein Designprinzip und schützt vor unbeabsichtigten Dependency-Updates in der CI.

Ein zweiter typischer Fehler ist das Fehlen von CSS-Klassen im kompilierten Output. Wenn Tailwind Template-Dateien nicht findet, generiert es nur die Base-Styles. Die Lösung: in der theme.css explizite @source-Pfade auf die Template-Verzeichnisse setzen. In Hyvä-Projekten sind das typischerweise die .phtml-Dateien im Theme-Verzeichnis und die Alpine.js-Komponenten. Ein dritter Fehler: den Build außerhalb des Theme-Verzeichnisses ausführen, sodass relative Pfade in der Tailwind-Konfiguration nicht aufgelöst werden können.

8. Frontend-Build-Strategien im Vergleich

Es gibt verschiedene Ansätze, den Frontend-Build in Magento-Projekten zu handhaben. Die Wahl hat erhebliche Auswirkungen auf Reproduzierbarkeit, Pipeline-Geschwindigkeit und Team-Komfort.

Strategie Reproduzierbar Pipeline-Geschwindigkeit Empfehlung
CSS lokal gebaut, ins Repo committed Nein Schnell (kein Build) Nicht empfohlen
Build auf dem Server beim Deploy Bedingt Langsam (kein Cache) Nicht empfohlen
Build in GitLab CI, Artefakt Ja Schnell mit Cache Empfohlen
Build in separatem Job, dann combiniert Ja Parallelisierbar Empfohlen für große Projekte
Docker-Image mit pre-built Assets Ja Sehr schnell Für Container-Deployments

9. Build-Zeit optimieren

Wenn der Frontend-Build-Job mehr als zwei Minuten dauert, gibt es mehrere Optimierungsansätze. Der wirksamste ist das korrekte npm-Caching: wenn node_modules aus dem Cache geladen wird, entfällt das Herunterladen komplett. Der zweite Ansatz ist die Einschränkung des Tailwind-Scan-Bereichs: mit expliziten @source-Pfaden auf die relevanten Template-Verzeichnisse scannt Tailwind nur wenige Hundert Dateien statt des gesamten Projekts.

Ein dritter Hebel ist die parallele Ausführung von PHP- und Frontend-Build. Wenn beide als separate Jobs in derselben Stage laufen, können sie auf verschiedenen Runnern gleichzeitig ausgeführt werden. Ein kombinierten Build-Job, der erst PHP-Abhängigkeiten installiert und dann den Frontend-Build ausführt, ist einfacher zu konfigurieren, aber langsamer als zwei parallele Jobs. Für Projekte mit langen Build-Zeiten lohnt sich die Auftrennung: der Deploy-Job wartet dann auf beide über needs: ["build:php", "build:frontend"].

10. Zusammenfassung

Der Frontend-Build mit npm und Tailwind CSS v4 gehört in die GitLab-Pipeline, nicht auf den Entwickler-Laptop oder den Produktionsserver. Die Kombination aus fixierter Node.js-Version, npm ci für deterministischen Dependency-Install, GitLab-Cache für node_modules und schlankem Artefakt für das kompilierte CSS ist der reproduzierbare Standard für Hyvä-Magento-Projekte. Diese Struktur garantiert, dass jeder Deployment auf jedem Server denselben CSS-Stand hat – unabhängig davon, wer den Build ausgelöst hat.

Die Integration des Static Content Deploy in denselben Build-Job bringt den vollständigen pub/static/-Ordner als Artefakt in die Pipeline, sodass der Zielserver keinen einzigen Build-Schritt mehr ausführen muss. Das macht Deployments schneller, reduziert die Serverlast während des Deployments und eliminiert eine ganze Klasse von Fehlern, die nur in Produktion auftreten, weil dort eine andere Node.js-Version installiert ist als auf dem Entwickler-Laptop.

Frontend-Builds in GitLab CI — Das Wichtigste auf einen Blick

Node.js-Version

.nvmrc + Docker-Image node:20-alpine in der Pipeline – identisch mit lokaler Entwicklungsumgebung.

npm ci statt npm install

Deterministischer Install aus package-lock.json, schlägt sofort fehl bei Abweichungen – Pflicht in CI.

Cache vs. Artefakt

node_modules in den Cache, kompiliertes CSS als Artefakt – nie umgekehrt.

Tailwind-Scan begrenzen

@source-Pfade in theme.css auf Template-Verzeichnisse begrenzen – halbiert die Build-Zeit in großen Projekten.

11. FAQ: npm, Tailwind und Frontend-Builds in GitLab

1Warum npm ci statt npm install in CI?
npm ci installiert exakt aus package-lock.json und schlägt fehl bei Abweichungen – Reproduzierbarkeit ist Pflicht in CI.
2Wie fixiere ich die Node.js-Version in GitLab CI?
image: node:20-alpine im Job + .nvmrc im Repository mit Inhalt "20" – lokal und in CI identisch.
3Was gehört in den Cache, was ins Artefakt?
node_modules → Cache. Kompiliertes CSS und pub/static/ → Artefakt. Nie umgekehrt.
4Wie beschleunige ich den Tailwind-Build in CI?
npm-Caching, @source-Pfade begrenzen und Frontend-Build parallel zum PHP-Build als separaten Job ausführen.
5Muss SCD auf dem Server ausgeführt werden?
In Hyvä: SCD im Build-Job in der Pipeline, pub/static/ als Artefakt. Kein SCD auf dem Zielserver nötig.
6Warum kein compiled CSS ins Git-Repository?
Artefakt, kein Quellcode. Unterschiedliche Node.js-Versionen erzeugen abweichende CSS-Dateien – Merge-Konflikte und inkonsistente Zustände folgen.
7Staging vs. Production: unterschiedliche Tailwind-Configs?
In Hyvä v4 mit CSS-first sind theme.css-Einstellungen umgebungsunabhängig. Umgebungsvariablen für Build-Scripts bei Bedarf.
8Was passiert, wenn package-lock.json fehlt?
npm ci schlägt sofort fehl – korrektes Verhalten. Lösung: lokal npm install, aktualisierte Lock-Datei committen.
9Wie lange sollten Frontend-Build-Artefakte gespeichert bleiben?
1–2 Tage für normale Pipelines. Release-Artefakte für Rollback explizit mit eigenem expire_in markieren.
10Kann Frontend-Build parallel zum PHP-Build laufen?
Ja – zwei separate Jobs in derselben Stage. Deploy-Job wartet auf beide über needs: ["build:php", "build:frontend"]. Schnellste Konfiguration.