14. Content Management
Content Management
Abschnitt betitelt „Content Management“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.
Was ein CMS ist und wofür
Abschnitt betitelt „Was ein CMS ist und wofür“Die Trennung, um die es geht
Abschnitt betitelt „Die Trennung, um die es geht“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.
Wann ein CMS passt
Abschnitt betitelt „Wann ein CMS passt“Nicht immer. Die Entscheidung hängt an zwei Fragen: Wer pflegt das, und wie oft ändert es sich?
| Art von Inhalt | Beispiel im Projekt | Gehört in |
|---|---|---|
| Fachdaten mit Logik | Buchungen, Maschinenverfügbarkeit | eigene Anwendung |
| Stammdaten mit Regeln | Benutzerkonten, Rollen | eigene Anwendung |
| Redaktioneller Text, wechselt | Hausordnung, Neuigkeiten, Anleitungen | CMS |
| Text, der sich praktisch nie ändert | Fehlermeldungen der Oberfläche | Übersetzungsdateien (Kapitel 12) |
| Reine Dokumentation für Entwickler | docs/ im Repository | statischer 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.
Was „Enterprise” bedeutet
Abschnitt betitelt „Was „Enterprise” bedeutet“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.
Marktüberblick
Abschnitt betitelt „Marktüberblick“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.
| System | Bauart | Bemerkung |
|---|---|---|
| WordPress | gekoppelt (headless möglich) | größte Verbreitung, riesiges Erweiterungsangebot, entsprechend großes Angriffsziel |
| TYPO3 | gekoppelt | im deutschsprachigen Raum bei Behörden und Hochschulen verbreitet, ausgeprägte Rechte- und Arbeitsablaufkonzepte, steile Lernkurve |
| Drupal | gekoppelt | sehr flexibles Inhaltsmodell, technisch anspruchsvoll |
| Joomla | gekoppelt | zwischen WordPress und TYPO3, rückläufige Verbreitung |
| Strapi | headless | Node.js, selbst betreibbar, schnell aufgesetzt |
| Directus | headless | legt sich über eine bestehende PostgreSQL-Datenbank, gute Rollenverwaltung |
| Payload | headless | TypeScript, Konfiguration im Code, passt gut zu Next.js |
| Sanity, Contentful | headless, gehostet | kein eigener Betrieb, dafür Abhängigkeit und laufende Kosten |
Auswählen und begründen
Abschnitt betitelt „Auswählen und begründen“Für das Jahresprojekt gelten diese Kriterien:
- Betreibbar auf Ihrem Server. Ein gehosteter Dienst nimmt Ihnen die Erfahrung weg, um die es hier geht.
- Rollen und Freigabe vorhanden. Ohne diese Fähigkeiten können Sie den Redaktionsteil des Kapitels nicht umsetzen.
- Mehrsprachigkeit vorhanden, wenn Ihr Projekt aus Kapitel 12 mehrsprachig ist.
- Aktive Wartung. Wann kam die letzte Sicherheitsaktualisierung, und wie schnell nach der Meldung?
- 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.
Installation und Betrieb
Abschnitt betitelt „Installation und Betrieb“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 serviceservices: 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.
Das Inhaltsmodell
Abschnitt betitelt „Das Inhaltsmodell“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.
Typen aus dem Projekt ableiten
Abschnitt betitelt „Typen aus dem Projekt ableiten“Gehen Sie von den Seiten aus, die es geben soll, und fragen Sie bei jeder: Was wiederholt sich hier, und was ist einmalig?
| Inhaltstyp | Felder | Anmerkung |
|---|---|---|
| Seite | Titel, Pfad, Blöcke, Sichtbarkeit | freie Seiten wie Hausordnung, Kontakt |
| Neuigkeit | Titel, Datum, Kurztext, Text, Bild | erscheint auf der Startseite |
| Maschinenbeschreibung | Verweis auf Maschinen-ID, Beschreibung, Sicherheitshinweise, Bilder | ergänzt den Datensatz der Anwendung |
| Anleitung | Titel, Kategorie, Schritte, Anhänge | mehrere pro Maschine möglich |
| Person | Name, Funktion, Foto, Kontakt | Werkstattaufsicht |
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.
Blöcke statt starrer Seiten
Abschnitt betitelt „Blöcke statt starrer Seiten“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, Medien, Metadaten
Abschnitt betitelt „Beziehungen, Medien, Metadaten“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.
Mehrsprachigkeit
Abschnitt betitelt „Mehrsprachigkeit“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.
Redaktion
Abschnitt betitelt „Redaktion“| Rolle | Darf | Darf nicht |
|---|---|---|
| Autor | eigene Entwürfe anlegen und bearbeiten, zur Prüfung geben | veröffentlichen, fremde Inhalte ändern |
| Redakteur | alle Inhalte bearbeiten, prüfen, zurückweisen | Benutzer und Inhaltstypen verwalten |
| Freigabe | veröffentlichen und zurücknehmen | Inhaltstypen ändern |
| Administration | alles, 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.
Der Ablauf
Abschnitt betitelt „Der Ablauf“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.
Der Redaktionsleitfaden
Abschnitt betitelt „Der Redaktionsleitfaden“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.
Darstellung
Abschnitt betitelt „Darstellung“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.
Beim headless-Ansatz holt Ihr Frontend die Inhalte über die Schnittstelle. Das ist ein ganz normaler Abruf, wie Sie ihn seit Kapitel 5 kennen, mit Zugriffstoken aus der Umgebung.
export async function loadPage(path: string, locale: string) { const response = await fetch( `${process.env.CMS_URL}/items/pages?filter[path][_eq]=${path}` + `&filter[status][_eq]=published&deep[translations][_filter][locale][_eq]=${locale}`, { headers: { Authorization: `Bearer ${process.env.CMS_TOKEN}` }, next: { tags: [`page:${path}`], revalidate: 3600 }, }, );
if (!response.ok) throw new Error(`CMS responded with ${response.status}`); const data = await response.json(); return data.data[0] ?? null;}Zwei Dinge daran sind wichtig. revalidate: 3600 heißt, dass die Seite höchstens eine Stunde alt sein kann, auch wenn sonst nichts passiert. Und tags erlaubt das gezielte Verwerfen, sobald sich im CMS etwas ändert. Dafür ruft das CMS nach jeder Änderung einen Webhook bei Ihnen auf:
import { revalidateTag } from "next/cache";
export async function POST(request: Request) { if (request.headers.get("x-cms-signature") !== process.env.CMS_HOOK_SECRET) { return new Response(null, { status: 401 }); }
const { collection, keys } = await request.json(); for (const key of keys ?? []) { revalidateTag(`${collection}:${key}`); } return Response.json({ revalidated: true });}Der Geheimnisvergleich ist keine Formalität. Ohne ihn kann jeder Ihren Cache beliebig oft verwerfen lassen, und Sie bauen die Seite bei jedem Aufruf neu.
Die Vorschau unveröffentlichter Stände braucht einen zweiten Weg: eine Route, die nach Prüfung eines Tokens den Vorschaumodus einschaltet, danach mit erhöhten Rechten auch Entwürfe abruft und diese Seiten niemals zwischenspeichert. Ohne diesen Schritt sieht die Redaktion ihre Arbeit erst nach der Freigabe.
Extensions
Abschnitt betitelt „Extensions“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.
Häufige Stolperfallen
Abschnitt betitelt „Häufige Stolperfallen“- Installieren und nie aktualisieren. Die häufigste Ursache übernommener Websites, mit Abstand.
- Alles zum Inhaltstyp machen, auch Buchungen. Danach haben Sie zwei Datenmodelle für dieselbe Sache und keines davon stimmt.
- Inhalte im Template verdrahten. Die Redaktion kommt nicht daran, und Sie sind wieder der Flaschenhals.
- Zwanzig Erweiterungen für Kleinigkeiten, also zwanzig zusätzliche Angriffsflächen und zwanzig Aktualisierungspfade.
- Kein Backup der Medienablage. Die Datenbank allein stellt nichts wieder her.
- Alle sind Administrator, weil die Rechtevergabe mühsam war.
- Alternativtexte optional gelassen. In Kapitel 15 fällt Ihnen das auf die Füße.
- Zugangsdaten im Repository, weil die Compose-Datei mitversioniert wurde.
- Keine Vorschau bei headless. Die Redaktion gibt frei, ohne gesehen zu haben, was sie freigibt.
- Cache ohne Verwerfen. Die Redakteurin ändert etwas, sieht es eine Stunde lang nicht und ruft an.
Lernergebnisse
Abschnitt betitelt „Lernergebnisse“- 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.
Arbeit am Jahresprojekt
Abschnitt betitelt „Arbeit am Jahresprojekt“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.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe - CMS auswählen und begründen
- Aufgabe - Inhaltsmodell für das eigene Projekt
- Aufgabe - Redaktionsrollen und Freigabeablauf
- Aufgabe - Headless-Anbindung mit Revalidierung