Zum Inhalt springen

14. Content Management

Zu Zen-Modus wechseln

Ihre Anwendung kann inzwischen buchen, anmelden, prüfen und ausliefern. Was sie nicht kann: eine Hausordnung aufnehmen, die sich zweimal im Jahr ändert. Eine Sicherheitsunterweisung, die jemand ohne Programmierkenntnisse ergänzen soll. Eine Nachricht auf der Startseite, weil die Fräse zwei Wochen ausfällt.

Für all das haben Sie bisher genau eine Antwort gehabt: Text im Quellcode ändern, bauen, ausliefern. Das funktioniert, solange Sie selbst der Einzige sind, der etwas ändern will. Sobald eine Werkstattleiterin die Öffnungszeiten anpassen möchte und dafür einen Entwickler braucht, ist die Architektur an einer Stelle falsch geschnitten.

Dieses Kapitel schneidet sie richtig.

Ein Content Management System trennt drei Dinge, die im handgeschriebenen Code untrennbar zusammenkleben: Inhalt (der Text der Hausordnung), Struktur (eine Hausordnung besteht aus Titel, Gültigkeitsdatum und Abschnitten) und Darstellung (wie das auf der Seite aussieht).

Wer den Inhalt pflegt, muss dann nichts über die anderen beiden wissen. Das ist die eigentliche Leistung. Nicht das schöne Editorfenster, sondern die Tatsache, dass eine Redakteurin einen Absatz ändern kann, ohne dass jemand ein Deployment auslöst.

Nicht immer. Die Entscheidung hängt an zwei Fragen: Wer pflegt das, und wie oft ändert es sich?

Art von InhaltBeispiel im ProjektGehört in
Fachdaten mit LogikBuchungen, Maschinenverfügbarkeiteigene Anwendung
Stammdaten mit RegelnBenutzerkonten, Rolleneigene Anwendung
Redaktioneller Text, wechseltHausordnung, Neuigkeiten, AnleitungenCMS
Text, der sich praktisch nie ändertFehlermeldungen der OberflächeÜbersetzungsdateien (Kapitel 12)
Reine Dokumentation für Entwicklerdocs/ im Repositorystatischer Seitengenerator

Die Grenze ist nicht immer scharf. Eine Maschinenbeschreibung mit Foto und Sicherheitshinweisen ist ein Grenzfall: Der Datensatz „Standbohrmaschine, Werkstatt 2, buchbar” gehört in Ihre Datenbank, der erklärende Text daneben kann aus dem CMS kommen. Genau dieser Fall wird weiter unten im Inhaltsmodell aufgelöst.

Der Zusatz klingt nach Marketing, meint aber etwas Konkretes. Ein CMS für ein Bastelblog braucht einen Benutzer und einen Knopf „Veröffentlichen”. Ein CMS für eine Organisation braucht:

  • Rechte und Rollen: Wer darf was, und zwar pro Bereich, nicht global.
  • Arbeitsabläufe: Ein Entwurf geht in die Prüfung, jemand anderer gibt frei.
  • Versionierung: Was stand vorige Woche da, und wie komme ich dorthin zurück.
  • Mandantenfähigkeit: Mehrere getrennte Bereiche in einer Installation.
  • Mehrsprachigkeit: Derselbe Inhalt in mehreren Sprachen, mit Übersetzungsstand.

Diese Merkmale sind der Grund, warum große Häuser TYPO3 oder Drupal einsetzen und nicht die einfachste verfügbare Lösung. Sie zahlen mit Einarbeitungszeit für Fähigkeiten, die sie ab einer bestimmten Organisationsgröße brauchen.

Zwei Bauarten stehen zur Wahl, und der Unterschied betrifft die Auslieferung der Seite.

flowchart LR
    subgraph Gekoppelt
        B1[Browser] --> C1[CMS]
        C1 --> T[Template im CMS]
        C1 --> D1[(CMS-Datenbank)]
    end
    subgraph Headless
        B2[Browser] --> N[Next.js]
        N -->|REST oder GraphQL| C2[CMS]
        C2 --> D2[(CMS-Datenbank)]
        N --> D3[(eigene Datenbank)]
    end

Ein gekoppeltes CMS liefert die Seite selbst aus. Es bringt Templates, Menüführung, Suche und Formulare mit, und man bekommt sehr schnell eine vollständige Website. Der Preis: Die Darstellung lebt in der Template-Sprache des CMS, und Ihr Next.js-Frontend ist eine zweite, getrennte Welt.

Ein headless CMS hat keine eigene Ausgabe. Es speichert Inhalte und stellt sie über eine Schnittstelle bereit, genau der Sorte, die Sie in Kapitel 4 bis 7 gebaut haben. Die Darstellung bleibt vollständig in Ihrem Frontend.

SystemBauartBemerkung
WordPressgekoppelt (headless möglich)größte Verbreitung, riesiges Erweiterungsangebot, entsprechend großes Angriffsziel
TYPO3gekoppeltim deutschsprachigen Raum bei Behörden und Hochschulen verbreitet, ausgeprägte Rechte- und Arbeitsablaufkonzepte, steile Lernkurve
Drupalgekoppeltsehr flexibles Inhaltsmodell, technisch anspruchsvoll
Joomlagekoppeltzwischen WordPress und TYPO3, rückläufige Verbreitung
StrapiheadlessNode.js, selbst betreibbar, schnell aufgesetzt
Directusheadlesslegt sich über eine bestehende PostgreSQL-Datenbank, gute Rollenverwaltung
PayloadheadlessTypeScript, Konfiguration im Code, passt gut zu Next.js
Sanity, Contentfulheadless, gehostetkein eigener Betrieb, dafür Abhängigkeit und laufende Kosten

Für das Jahresprojekt gelten diese Kriterien:

  1. Betreibbar auf Ihrem Server. Ein gehosteter Dienst nimmt Ihnen die Erfahrung weg, um die es hier geht.
  2. Rollen und Freigabe vorhanden. Ohne diese Fähigkeiten können Sie den Redaktionsteil des Kapitels nicht umsetzen.
  3. Mehrsprachigkeit vorhanden, wenn Ihr Projekt aus Kapitel 12 mehrsprachig ist.
  4. Aktive Wartung. Wann kam die letzte Sicherheitsaktualisierung, und wie schnell nach der Meldung?
  5. Passt zur Bauart Ihres Projekts. Haben Sie ein Next.js-Frontend, ist headless der geradere Weg. Soll der Inhaltsbereich als eigene Website unter eigener Subdomain stehen, spricht viel für ein gekoppeltes System.

Die Wahl kommt als Entscheidungsprotokoll nach docs/entscheidungen.md, im Format aus Kapitel 1: Alternativen, Kriterien, Entscheidung, Konsequenzen. „Haben wir im Internet gefunden” ist keine Begründung.

Das CMS ist ein weiterer Dienst neben Ihrer Anwendung, kein Teil davon. Eigener Container, eigene Datenbank, eigene Subdomain, eigene Sicherung.

# Extract: the CMS as an additional service
services:
cms:
image: directus/directus:11
environment:
DB_CLIENT: pg
DB_HOST: cms-db
DB_DATABASE: cms
DB_USER: cms
DB_PASSWORD: ${CMS_DB_PASSWORD}
SECRET: ${CMS_SECRET}
ADMIN_EMAIL: ${CMS_ADMIN_EMAIL}
ADMIN_PASSWORD: ${CMS_ADMIN_PASSWORD}
PUBLIC_URL: https://inhalt.projekt.example.at
volumes:
- cms-uploads:/directus/uploads
depends_on:
cms-db:
condition: service_healthy
labels:
- traefik.enable=true
- traefik.http.routers.cms.rule=Host(`inhalt.projekt.example.at`)
- traefik.http.routers.cms.tls.certresolver=le
cms-db:
image: postgres:17-alpine
environment:
POSTGRES_DB: cms
POSTGRES_USER: cms
POSTGRES_PASSWORD: ${CMS_DB_PASSWORD}
volumes:
- cms-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U cms"]
interval: 10s
volumes:
cms-uploads:
cms-data:

Dieselben Regeln wie in Kapitel 3: Die Datenbank hat keinen ports:-Eintrag, die Zugangsdaten stehen in der Umgebung und nicht im Repository, und die beiden Volumes gehören in Ihren Sicherungsplan. Ein Backup der CMS-Datenbank ohne die hochgeladenen Dateien ist wertlos, weil danach jeder Artikel auf ein Bild zeigt, das es nicht mehr gibt.

Ein CMS bringt seine eigene Benutzerverwaltung mit. Ihre Redakteure haben also ein zweites Konto neben dem aus Kapitel 8. Das ist für dieses Jahr in Ordnung und muss nur bewusst geschehen: aufschreiben, wer welche Konten hat, und beim Ausscheiden beide sperren. Die Zusammenführung über einen gemeinsamen Anmeldedienst ist ein Thema der 5. Klasse.

Hier entscheidet sich, ob das CMS hilft oder im Weg steht. Ein Inhaltsmodell ist dasselbe wie ein Datenmodell, nur mit anderen Benutzern: Statt Ihres Codes arbeiten Menschen damit, die den Aufbau weder ändern noch umgehen können.

Gehen Sie von den Seiten aus, die es geben soll, und fragen Sie bei jeder: Was wiederholt sich hier, und was ist einmalig?

InhaltstypFelderAnmerkung
SeiteTitel, Pfad, Blöcke, Sichtbarkeitfreie Seiten wie Hausordnung, Kontakt
NeuigkeitTitel, Datum, Kurztext, Text, Bilderscheint auf der Startseite
MaschinenbeschreibungVerweis auf Maschinen-ID, Beschreibung, Sicherheitshinweise, Bilderergänzt den Datensatz der Anwendung
AnleitungTitel, Kategorie, Schritte, Anhängemehrere pro Maschine möglich
PersonName, Funktion, Foto, KontaktWerkstattaufsicht

Der dritte Eintrag ist die Antwort auf den Grenzfall von vorhin. Die Maschine selbst bleibt in Ihrer Datenbank, weil Buchungen per Fremdschlüssel darauf zeigen und weil Verfügbarkeit eine Frage von Logik ist. Die Beschreibung lebt im CMS und ist über die ID verbunden. Beide Seiten kennen dieselbe Zahl, sonst nichts.

Ein Inhaltstyp „Seite” mit den Feldern Titel, Einleitung, Bild und Fließtext funktioniert genau so lange, bis jemand zwei Bilder braucht. Danach kommt „Bild 2”, dann „Bild 3”, und nach einem Jahr hat der Typ vierzig Felder, von denen bei jeder Seite fünf befüllt sind.

Der Ausweg sind Blöcke: Eine Seite hat eine geordnete Liste von Bestandteilen, und jeder Bestandteil hat einen Typ (Text, Bild, Bildergalerie, Zitat, Tabelle, Hinweiskasten, Einbettung). Die Redaktion setzt daraus zusammen, was sie braucht, ohne dass Sie etwas ändern. Ihr Frontend rendert pro Blocktyp eine Komponente, und mehr braucht es nicht.

Der Preis dafür: Jeder neue Blocktyp braucht eine Komponente. Fangen Sie mit fünf an, nicht mit fünfzehn.

Beziehungen verbinden Inhalte untereinander, also die Anleitung mit der Maschinenbeschreibung, die Neuigkeit mit ihrem Verfasser. Was einmal gepflegt und dann verwiesen wird, kann nicht auseinanderlaufen.

Die Medienverwaltung ist ein eigener Bereich mit eigenen Regeln. Jedes Bild braucht einen Alternativtext, und zwar bei der Ablage, nicht bei der Verwendung. Wenn Sie das Feld erst im Kapitel 15 nachrüsten, haben Sie zweihundert Bilder ohne Alternativtext und niemanden, der sie nachträglich beschreibt.

Metadaten sind die Felder, die nicht im Text erscheinen: Seitentitel und Beschreibung für Suchmaschinen, Vorschaubild für geteilte Links, Veröffentlichungsdatum, Sichtbarkeit.

Kapitel 12 hat die Regel aufgestellt: Oberflächentexte in Übersetzungsdateien, Inhalte in der Datenbank. Der zweite Teil wird jetzt konkret.

Jedes CMS mit Mehrsprachigkeit legt Übersetzungen als eigene Datensätze an, die auf denselben Originalinhalt zeigen, dazu einen Übersetzungsstand pro Sprache. Daraus ergeben sich zwei Fragen, die Sie beantworten müssen, weil kein System sie für Sie beantwortet:

  • Was passiert, wenn eine Übersetzung fehlt? Ausweichen auf die Hauptsprache, oder den Eintrag in dieser Sprache gar nicht anzeigen? Für eine Neuigkeit ist Ausweichen sinnvoll, für eine Sicherheitsunterweisung fragwürdig.
  • Was passiert, wenn sich das Original ändert? Die Übersetzung ist dann veraltet, ohne dass jemand es sieht. Systeme mit Übersetzungsstand markieren das; ohne solche Unterstützung braucht die Redaktion eine Absprache.
RolleDarfDarf nicht
Autoreigene Entwürfe anlegen und bearbeiten, zur Prüfung gebenveröffentlichen, fremde Inhalte ändern
Redakteuralle Inhalte bearbeiten, prüfen, zurückweisenBenutzer und Inhaltstypen verwalten
Freigabeveröffentlichen und zurücknehmenInhaltstypen ändern
Administrationalles, auch Struktur und Benutzer

Das ist wieder Kapitel 8, nur an anderer Stelle: Rollen bündeln Rechte, und die Prüfung findet im System statt und nicht in einer Absprache. Der Unterschied zu Kapitel 8: Diese Rechteverwaltung müssen Sie nicht bauen, sondern konfigurieren. Und weil sie konfiguriert ist, muss sie jemand konfiguriert haben. Ein CMS, in dem alle vier Personen Administrator sind, hat kein Rollenkonzept.

stateDiagram-v2
    [*] --> Entwurf
    Entwurf --> InPruefung: zur Prüfung geben
    InPruefung --> Entwurf: zurückgewiesen
    InPruefung --> Freigegeben: freigegeben
    Freigegeben --> Veroeffentlicht: veröffentlicht
    Veroeffentlicht --> Entwurf: neue Fassung
    Veroeffentlicht --> Zurueckgezogen: zurückgenommen
    Zurueckgezogen --> Entwurf

Zwei Punkte fallen dabei gerne unter den Tisch. Erstens: Wenn jemand eine veröffentlichte Seite bearbeitet, entsteht eine neue Fassung, und die alte bleibt online, bis die neue freigegeben ist. Ein System, in dem Bearbeiten sofort live geht, hat keinen Arbeitsablauf, sondern nur ein Formular.

Zweitens: Vorschau. Wer einen Entwurf nicht ansehen kann, gibt ihn blind frei. Bei einem gekoppelten CMS ist das eingebaut. Bei headless müssen Sie es bauen, und das steht im nächsten Abschnitt.

Eine Seite, mehr nicht, aber sie muss existieren. Inhalt: Wer darf was. Wie eine Überschrift formuliert wird (Sie oder du, Kapitel 12). Wie lang ein Kurztext sein darf, bevor die Kachel umbricht. Welche Bildgrößen die Vorlagen erwarten. Dass jedes Bild einen Alternativtext bekommt. Wen man fragt, wenn etwas nicht geht.

Ohne diesen Zettel erklärt jedes Teammitglied jeder Redakteurin etwas anderes, und nach drei Monaten sieht die Website danach aus.

Bei einem gekoppelten CMS passen Sie die Templates an. Legen Sie dafür ein eigenes Theme an, statt ein mitgeliefertes zu verändern. Der Grund ist derselbe wie bei Bibliotheken: Die nächste Aktualisierung überschreibt Ihre Änderungen, und dann stehen Sie vor der Wahl, entweder nicht zu aktualisieren oder Ihre Arbeit zu verlieren.

Was in den Templates nichts zu suchen hat, ist Text. Sobald „Öffnungszeiten: Mo bis Fr” im Template steht, kommt die Redaktion nicht mehr daran, und genau dafür haben Sie das System aufgesetzt.

Erweiterungen sind der Grund, warum CMS-Ökosysteme so groß sind, und gleichzeitig der häufigste Weg, auf dem eine Installation übernommen wird. Eine Erweiterung läuft mit den Rechten des CMS. Sie sieht dieselbe Datenbank, dieselben Dateien, dieselben Sitzungen.

Prüfen Sie vor jeder Installation:

  • Wann kam die letzte Aktualisierung? Ein Jahr Stillstand bei einer Erweiterung, die HTML entgegennimmt, ist ein Ausschlussgrund.
  • Wie viele Installationen, wie viele offene Fehlermeldungen, wie reagiert die Entwicklung auf Sicherheitsmeldungen?
  • Welche Rechte verlangt sie? Ein Kontaktformular braucht keinen Dateisystemzugriff.
  • Gibt es das auch als Bordmittel? Ein Modul für eine Aufgabe, die zehn Zeilen im Template lösen, ist ein schlechtes Geschäft.

Für das Jahresprojekt binden Sie eine Erweiterung ein, konfigurieren sie und schreiben diese Prüfung dazu. Eine eigene Erweiterung zu schreiben ist Stoff der 5. Klasse; wer den Einstieg jetzt schon sucht, beginnt mit einer kleinen Anpassung im eigenen Theme oder einem eigenen Blocktyp.

  1. Installieren und nie aktualisieren. Die häufigste Ursache übernommener Websites, mit Abstand.
  2. Alles zum Inhaltstyp machen, auch Buchungen. Danach haben Sie zwei Datenmodelle für dieselbe Sache und keines davon stimmt.
  3. Inhalte im Template verdrahten. Die Redaktion kommt nicht daran, und Sie sind wieder der Flaschenhals.
  4. Zwanzig Erweiterungen für Kleinigkeiten, also zwanzig zusätzliche Angriffsflächen und zwanzig Aktualisierungspfade.
  5. Kein Backup der Medienablage. Die Datenbank allein stellt nichts wieder her.
  6. Alle sind Administrator, weil die Rechtevergabe mühsam war.
  7. Alternativtexte optional gelassen. In Kapitel 15 fällt Ihnen das auf die Füße.
  8. Zugangsdaten im Repository, weil die Compose-Datei mitversioniert wurde.
  9. Keine Vorschau bei headless. Die Redaktion gibt frei, ohne gesehen zu haben, was sie freigibt.
  10. Cache ohne Verwerfen. Die Redakteurin ändert etwas, sieht es eine Stunde lang nicht und ruft an.
  • Erklären: die Trennung von Inhalt, Struktur und Darstellung erläutern und begründen, wem sie nützt.
  • Beurteilen: entscheiden, welche Inhalte des eigenen Projekts in ein CMS gehören und welche in die Anwendung.
  • Beurteilen: gekoppelte und headless Systeme gegeneinander abwägen und die eigene Wahl protokollieren.
  • Anwenden: ein CMS als eigenen Dienst installieren, über eine Subdomain veröffentlichen und absichern.
  • Erschaffen: ein Inhaltsmodell mit Typen, Feldern, Beziehungen und Blöcken entwerfen.
  • Anwenden: Redaktionsrollen und einen Freigabeablauf einrichten und nachweisen.
  • Anwenden: Templates anpassen oder Inhalte headless einbinden, samt Verwerfen über Webhook.
  • Beurteilen: eine Erweiterung nach Wartung, Verbreitung und verlangten Rechten bewerten.

Der Inhaltsbereich läuft im CMS und ist an die Anwendung angebunden:

  • Auswahlentscheidung in docs/entscheidungen.md, mit Alternativen und Kriterien.
  • CMS als eigener Dienst mit eigener Datenbank, eigener Subdomain und in den Sicherungsplan aufgenommen.
  • Inhaltsmodell mit mindestens vier Typen, davon einer mit Blöcken und einer mit Verweis auf ein Objekt Ihrer Anwendung.
  • Mindestens zwei Redaktionsrollen mit unterschiedlichen Rechten und ein Freigabeablauf, nachgewiesen an einem Beispiel.
  • Mindestens fünf gepflegte Seiten, angelegt von jemandem, der nicht der Entwickler ist.
  • Angepasste Darstellung oder Headless-Anbindung mit funktionierendem Verwerfen.
  • Eine eingebundene Erweiterung samt Prüfnotiz.
  • Ein Redaktionsleitfaden auf einer Seite.
  • Aufgabe - CMS auswählen und begründen
  • Aufgabe - Inhaltsmodell für das eigene Projekt
  • Aufgabe - Redaktionsrollen und Freigabeablauf
  • Aufgabe - Headless-Anbindung mit Revalidierung