ignis-cms
v0.1.0
Published
Ein leichtgewichtiges, git-basiertes CMS als npm-Package für Next.js App Router Projekte.
Readme
ignis-cms
Ein leichtgewichtiges, git-basiertes CMS als npm-Package für Next.js App Router Projekte.
Das Package liest YAML-Dateien aus einem /content-Verzeichnis, generiert automatisch eine CMS-Oberfläche unter /admin, und schreibt Änderungen per GitHub API direkt ins Repository zurück.
Kernprinzipien
- Git ist die Datenbank. Alle Inhalte leben als YAML-Dateien im Repo. Versionierung und Rollback kommen gratis über Git.
- Zero Config. Package installieren, 4 Dateien anlegen, Env-Vars setzen — fertig. Kein Setup-Wizard, keine Config-Datei.
- Schema-Ableitung. Feldtypen werden automatisch aus dem YAML-Wert ermittelt. Kein separates Schema nötig.
- GitHub API statt lokalem Filesystem. Funktioniert auf Vercel und allen serverlosen Hosts ohne Einschränkungen.
Installation
npm install ignis-cmsSetup (4 Dateien + Env-Vars)
1. Admin-UI
CmsPage muss im Layout leben, damit der Zustand bei der Navigation erhalten bleibt.
// app/admin/layout.tsx
import { CmsPage } from "ignis-cms/ui";
export default function AdminLayout() {
return <CmsPage />;
}// app/admin/[[...path]]/page.tsx
export default function AdminPage() {
return null; // Layout übernimmt alles
}2. API-Route
// app/api/cms/[...action]/route.ts
export const runtime = "nodejs";
export { GET, POST, PUT, DELETE, PATCH } from "ignis-cms/api";3. Auth-Route
// app/api/auth/[...nextauth]/route.ts
import { cmsAuthHandler } from "ignis-cms/api";
export { cmsAuthHandler as GET, cmsAuthHandler as POST };4. Next.js Konfiguration
// next.config.ts
const nextConfig = {
transpilePackages: ["ignis-cms"],
};
export default nextConfig;5. Umgebungsvariablen
# .env.local
# NextAuth (Pflicht)
NEXTAUTH_URL=http://localhost:3001
NEXTAUTH_SECRET=<zufälliger-geheimer-string>
# GitHub API — content reads and writes (required)
# Create a fine-grained token at https://github.com/settings/tokens
# Required permissions: Contents (read & write), Metadata (read)
GITHUB_TOKEN=<fine-grained-personal-access-token>
GITHUB_OWNER=<org-oder-username>
GITHUB_REPO=<repo-name>
GITHUB_BRANCH=main
# In a monorepo, set to the path of the Next.js app (e.g. "apps/demo")
# Leave empty if the app is at the repo root
GITHUB_APP_BASE_PATH= # Leer wenn App im Repo-Root; z. B. "apps/demo" im Monorepo
GITHUB_COMMITTER_NAME=ignis CMS
[email protected]
# MailerSend — password reset emails (optional)
# Get your API key at https://app.mailersend.com/api-tokens
MAILERSEND_API_KEY=
MAILERSEND_FROM_EMAIL=
MAILERSEND_FROM_NAME=
# AI YAML-Assistent — optional (OpenRouter)
OPENROUTER_API_KEY=GitHub Token Berechtigungen: Contents (Read & Write), Metadata (Read).
Erster Start — Auto-Init
Beim ersten Aufruf des CMS (kein /content-Ordner im Repo) wird automatisch ein Grundgerüst angelegt:
content/
settings.yaml ← Globale Einstellungen (Firmenname, SEO, Header, Footer)
_assets.yaml ← Asset-Index (wird automatisch verwaltet)
pages/
home.yaml ← Starter-SeiteKein manuelles Anlegen nötig — einfach aufrufen und loslegen.
Benutzer verwalten
Benutzer werden in content/_users.yaml verwaltet. Im CMS selbst ist die Benutzerverwaltung für Admins über die Seitenleiste erreichbar.
# content/_users.yaml
users:
- email: [email protected]
password: "scrypt:SALT:HASH"
role: admin
- email: [email protected]
password: "scrypt:SALT:HASH"
role: editorPasswort-Hash erzeugen:
import { hashPassword } from "ignis-cms";
console.log(hashPassword("mein-sicheres-passwort"));
// → scrypt:abc123...:def456...Zwei Rollen:
- admin — Neue Dokumente anlegen, löschen, Benutzer verwalten
- editor — Nur bestehende Dokumente bearbeiten
Content-Struktur
content/
blog/ ← Collection (Ordner = mehrere Dokumente)
post-1.yaml
post-2.yaml
pages/ ← Collection
home.yaml
about.yaml
settings.yaml ← Singleton (direkt in /content, kein Ordner)
_assets.yaml ← Reserviert (Asset-Index, kein Content)
_users.yaml ← Reserviert (Benutzer, kein Content)Dateien mit _-Prefix sind reserviert und erscheinen nicht als Content in der UI.
Schema-Ableitung
Feldtypen werden automatisch aus dem YAML-Wert ermittelt:
| YAML-Wert | Feld-Typ | UI-Komponente |
|---|---|---|
| "Einzeiliger Text" | text | Text Input |
| mehrzeiliger String mit \| | richtext | Textarea |
| "/uploads/bild.jpg" | image | Asset Picker (Bilder) |
| "/uploads/doc.pdf" | file | Asset Picker (alle Typen) |
| 2025-03-15 | date | Date Picker |
| true / false | boolean | Toggle |
| 42 / 3.14 | number | Number Input |
| ["a", "b"] | list | Tag-Liste |
| [{label: "X", url: "/"}] | list_objects | Verschachteltes Formular |
| {title: "...", body: "..."} | group | Fieldset / Accordion |
Schema-Override (optional)
Für Select-Felder oder Validierung kann pro Collection eine _schema.yaml angelegt werden:
# content/blog/_schema.yaml
fields:
category:
type: select
options:
- tutorial
- news
- reviewYAML-Editor
Jedes Dokument lässt sich über den YAML-Button in der Toolbar als Roh-YAML bearbeiten. Der Editor bietet:
- Syntax-Highlighting
- Echtzeit-Fehler- und Warnungsanzeige
- Formatieren-Button (nur bei gültigem YAML aktiv)
- Tab fügt 2 Leerzeichen ein
Wechsel zwischen Formular- und YAML-Modus ist verlustfrei — Änderungen werden sofort übernommen.
AI YAML-Assistent
Wenn OPENROUTER_API_KEY gesetzt ist, erscheint im YAML-Editor eine KI-Eingabezeile. Eingabe in natürlicher Sprache — das Modell (Claude 3.5 Haiku via OpenRouter) ergänzt oder verändert das YAML entsprechend.
Beispiele:
- „Füge ein neues Feld og_image hinzu"
- „Ändere den Titel auf Hallo Welt"
- „Ergänze drei weitere Navigationseinträge"
Asset Library
Dateien werden nach /public/uploads/ hochgeladen (max. 10 MB). Metadaten (Titel, Alt-Text) werden automatisch in content/_assets.yaml gespeichert. Image- und File-Felder öffnen einen Asset Picker.
Erlaubte Dateitypen: JPG, PNG, GIF, WebP, SVG, AVIF, PDF, DOC, DOCX, XLS, XLSX.
Passwort-Reset per E-Mail (optional)
Wenn MAILERSEND_API_KEY gesetzt ist, erscheint auf der Login-Seite ein „Passwort vergessen"-Link. Der Reset-Link ist 1 Stunde gültig und wird über MailerSend versendet.
Tastaturkürzel
| Kürzel | Funktion |
|---|---|
| Cmd+S / Ctrl+S | Dokument speichern |
Git-Commits
Jede schreibende Operation erzeugt automatisch einen Git Commit via GitHub API:
| Operation | Commit Message |
|---|---|
| Dokument anlegen | content: create blog/post-1 |
| Dokument bearbeiten | content: update blog/post-1 |
| Dokument duplizieren | content: create blog/post-1-kopie |
| Dokument löschen | content: delete blog/post-1 |
| Asset hochladen | assets: upload hero.jpg |
| Asset-Metadaten | assets: update metadata hero.jpg |
| Asset löschen | assets: delete hero.jpg |
| Passwort geändert | users: update account [email protected] |
| Passwort zurückgesetzt | users: reset password [email protected] |
Anforderungen
- Next.js 14+ (App Router)
- Node.js 18+
- GitHub Repository (Public oder Private)
- Fine-grained GitHub Token: Contents (Read & Write), Metadata (Read)
Lizenz
MIT
ignis CMS — Migration Prompt
Diesen Prompt an eine KI übergeben, um eine bestehende Website ins ignis CMS zu migrieren.
Du migrierst eine bestehende Website ins ignis CMS. Das ignis CMS speichert alle Inhalte als YAML-Dateien in einem `/content`-Verzeichnis und schreibt sie via GitHub API ins Repository.
## Deine Aufgabe
Analysiere die Website (URL, Screenshot, HTML oder Beschreibung) und erstelle für jede Seite eine strukturierte YAML-Datei. Alle Inhalte werden 1:1 übernommen — kein Text wird verändert, gekürzt oder umformuliert.
## Dateistruktur
content/ pages/ home.yaml ← Startseite about.yaml ← Über uns leistungen.yaml ← Leistungsseite kontakt.yaml ← Kontaktseite [weitere].yaml settings.yaml ← Globale Einstellungen (Firmenname, SEO, Navigation, Footer)
Kein `globals/`-Ordner. Navigation, Footer und SEO-Defaults gehören alle in `settings.yaml`.
## settings.yaml Struktur
```yaml
companyName: "Firmenname GmbH"
seo:
title: "Firmenname GmbH"
description: "Kurze Beschreibung für Google (max. 160 Zeichen)."
keywords: "keyword1, keyword2"
ogImage: "/uploads/og-image.jpg"
header:
logo: "/uploads/logo.svg"
navigation:
- label: "Startseite"
url: "/"
- label: "Leistungen"
url: "/leistungen"
- label: "Über uns"
url: "/ueber-uns"
- label: "Kontakt"
url: "/kontakt"
cta:
label: "Anfragen"
url: "/kontakt"
footer:
tagline: "Ihr Partner für..."
links:
- label: "Impressum"
url: "/impressum"
- label: "Datenschutz"
url: "/datenschutz"
copyright: "© 2025 Firmenname GmbH"Sektions-Typen
Erkenne Sektionen anhand der visuellen Struktur. Typische Sektionen (beispielsweise, nicht exklusiv):
hero— Hauptbanner: Überschrift, Subtext, CTA-Button, Bild/Videointro— Kurze Einleitung, oft zentriertfeatures— Vorteile/Leistungen als Liste von Objektenabout— Über-uns-Abschnitt, Bild + Textteam— Personen-Grid (Liste mit name, role, image)testimonials— Kundenstimmen (Liste mit quote, author, company)gallery— Bildergalerie (Liste von Bild-Pfaden)cta— Call-to-Action-Blockcontact— Kontaktinformationenfaq— Häufige Fragen (Liste mit question + answer)stats— Kennzahlen (Liste mit value + label)logos— Partner-Logos (Liste von Bild-Pfaden)text— Reiner Textblock (für rechtliche Seiten)
Feldtypen
# Einzeilige Texte → einfache Strings
title: "Willkommen bei uns"
button_text: "Jetzt anfragen"
button_url: "/kontakt"
# Mehrzeilige Texte / Absätze → Block-Scalar mit |
body: |
Hier steht ein längerer Text.
Er kann über mehrere Zeilen gehen.
# Bilder → Pfad (Platzhalter wenn noch kein Upload)
image: "/uploads/hero-bild.jpg"
# Video-URLs
video_url: "https://www.youtube.com/watch?v=..."
video_url: "/uploads/hero-video.mp4"
# Booleans
visible: true
# Listen von Strings
tags:
- "Solar"
- "Elektro"
# Listen von Objekten (Features, Team, FAQ, etc.)
items:
- title: "Leistung 1"
description: "Beschreibung der Leistung."
icon: "check"
- title: "Leistung 2"
description: "Weitere Leistung."
icon: "shield"YAML-Template pro Seite
# Seitenweite SEO (überschreibt settings.yaml Defaults)
seo:
title: "Seitentitel | Firmenname"
description: "Kurze Beschreibung der Seite (max. 160 Zeichen)."
hero:
title: "Hauptüberschrift"
subtitle: "Kurzer Satz darunter"
body: |
Optionaler längerer Text.
image: "/uploads/hero.jpg"
button:
text: "Jetzt kontaktieren"
href: "/kontakt"
intro:
title: "Wer wir sind"
body: |
Fließtext der Einleitung.
features:
title: "Unsere Leistungen"
items:
- title: "Leistung 1"
description: "Beschreibung."
icon: "zap"
- title: "Leistung 2"
description: "Beschreibung."
icon: "shield"
ctaSection:
title: "Bereit loszulegen?"
body: "Kontaktieren Sie uns noch heute."
button:
text: "Jetzt kontaktieren"
href: "/kontakt"Bilder
Mit Tool-Zugriff (empfohlen)
# Bild herunterladen und hochladen
curl -L "https://quell-website.de/images/hero.jpg" -o /tmp/hero.jpg
curl -X POST "http://localhost:3001/api/cms/upload" \
-H "Cookie: <session-cookie>" \
-F "file=@/tmp/hero.jpg;filename=hero.jpg"
# Antwort: { "ok": true, "path": "/uploads/hero.jpg", "asset": { ... } }- Website analysieren → alle Bild-URLs sammeln
- Für jedes Bild: herunterladen → hochladen → zurückgegebenen Pfad im YAML verwenden
- YouTube/Vimeo: URL direkt als String (kein Download)
- Selbst gehostete Videos: herunterladen → hochladen wie Bilder
Dateinamen: Englisch, kebab-case (hero-solar-panel.jpg, team-max-mueller.jpg).
Ohne Tool-Zugriff
- Platzhalter eintragen:
/uploads/beschreibender-name.jpg - Am Ende Liste aller Platzhalter für manuellen Upload ausgeben
Wichtige Regeln
- Kein Inhalt weglassen — Jeder Text, jedes Bild, jede Information übernehmen
- Keine Texte verändern — 1:1, kein Umformulieren, kein Kürzen
- Kein
globals/-Ordner — Navigation, Footer und Default SEO gehören insettings.yaml - Sinnvolle Sektions-Benennung — Nicht alle Felder flach auf oberster Ebene
- Wiederholende Inhalte → immer als
items-Liste mit Objekten - Konsistente Feldnamen über alle Seiten: immer
title, nie abwechselndheadline/heading
Ausgabe
Mit Tool-Zugriff
- Bilder herunterladen und hochladen
- YAML-Dateien direkt in
content/schreiben - Kurze Zusammenfassung: Seiten migriert, Bilder hochgeladen
Ohne Tool-Zugriff
Pro Seite:
- Dateipfad als Kommentar:
# content/pages/home.yaml - Vollständiger YAML-Inhalt
- Am Ende: Liste aller Bild-Platzhalter
Reihenfolge: settings.yaml → Startseite → weitere Seiten
