Inhalte aus Dateien
Bausteine mit source files lesen Markdown, YAML, JSON und CSV aus content/. Dateiaufbau, Frontmatter, Datendateien, Entwürfe, alte Adressen, Links und Fehler.
Wofür Dateien
Ein Baustein mit source: 'files' liest seine Einträge aus Dateien im Repo: Markdown, YAML, JSON oder CSV. Nutze das für Inhalte, die Entwickler pflegen: Doku, Blog, Rechtstexte, eine Preisliste. Diese Doku nutzt es selbst.
Die Seite liest Datei-Inhalte genauso wie Plattform-Inhalte: mit useGinko und useGinkoEntry. Beim Build liest Ginko die Dateien und baut daraus dasselbe Bundle wie die Plattform. Sichtbarkeit, Reihenfolge, Adressen, Sprachen und Fallback gelten unverändert.
Ein Baustein hat genau eine Quelle. Die Plattform sieht Datei-Bausteine nie. Eine Seite, deren Bausteine alle aus Dateien kommen, fragt die Plattform nie und braucht keine Plattform-Adresse.
Einrichten
Du brauchst das Paket @lupinum/ginko-content in den Abhängigkeiten der Seite. Fehlt es, bricht der Build mit einer Meldung ab, die sagt, was du installieren musst.
Du beschreibst den Baustein wie jeden anderen und setzt source: 'files'. Der Text der Datei füllt ein Feld vom Typ f.markdown:
import { collection, defineSite, f } from '@lupinum/ginko'
export default defineSite({
site: 'ginko-docs',
name: 'Ginko',
url: 'https://ginko.lupinum.com',
timeZone: 'Europe/Vienna',
locales: ['de', 'en'],
currency: 'EUR',
pages: { home: { de: '/', en: '/en' } },
bricks: {
docs: collection({
label: 'Doku',
source: 'files',
max: 500,
order: 'manual',
route: { de: '/doku/[slug]', en: '/en/docs/[slug]' },
fields: {
title: f.text('Titel'),
description: f.longtext('Beschreibung', { optional: true }),
slug: f.slug('Adresse', { from: 'title' }),
body: f.markdown('Text'),
},
}),
},
})Diese Regeln gelten für Datei-Bausteine:
- Ein Datei-Baustein hat höchstens ein Markdown-Feld: den Text der Datei. In einer Liste ist es dort nicht erlaubt.
profilelegt fest, was der Text darf:inline,basic,rich(Standard) oderpage(auch Komponenten wie::callout). Rohes HTML bleibt Text.- Ein Slug-Feld muss
slugheißen.
Dateiaufbau
Die Dateien liegen im Ordner content/ neben der nuxt.config.ts:
content/
docs/ # Sammlung "docs"
1.einstieg.md # Hauptsprache. Die Zahl ist die Reihenfolge.
1.einstieg.en.md # Übersetzung: gleicher Name plus .en
2.bausteine.md
rechtliches.md # Einzelstück "rechtliches"
rechtliches.en.md- Eine Sammlung hat einen Ordner
content/<baustein>/. Jede Datei ist ein Eintrag. - Ein Einzelstück ist eine Datei
content/<baustein>.md. - Die Datei der Hauptsprache hat kein Sprachkürzel. Eine Übersetzung heißt gleich und endet auf
.<sprache>.md, zum Beispiel.en.md. - Die Zahl vor dem Namen ist die Reihenfolge. Dateien ohne Zahl kommen danach, nach Namen sortiert. Die Zahl bestimmt die Reihenfolge bei
order: 'manual'. - Die Id eines Eintrags ist der Pfad ohne Zahl, Sprache und Endung, zum Beispiel
docs/einstieg. - Bilder und Dateien liegen im Ordner
public/.
Eine Übersetzung ohne Datei der Hauptsprache ist ein Fehler.
Frontmatter
Das Frontmatter ist ein Block in YAML am Anfang der Datei. Es füllt die Felder des Bausteins in einfacher Form. Der Text unter dem Frontmatter füllt das Markdown-Feld. Das Markdown-Feld selbst steht nie im Frontmatter.
---
title: Einstieg
description: Was Ginko ist.
---
## Was Ginko ist
Ginko verbindet eine Nuxt-Seite mit ihren Inhalten.So schreibst du die einzelnen Typen:
| Typ | Form im Frontmatter |
|---|---|
text, longtext | Text. Die Sprache kommt aus dem Dateinamen. |
number, boolean | 42 oder true |
money | Betrag als Zahl: 12.50 |
date, time | 2026-10-12 und 18:30 |
daterange | from: 2026-10-12 und to: 2026-10-14, eingerückt unter dem Feldnamen |
choice | der Wert der Option |
choices, list | eine YAML-Liste. Eine Listenzeile ist ein Objekt mit den Spalten. |
image | src (Pfad unter public/) und alt, eingerückt unter dem Feldnamen |
file | Pfad unter public/ |
ref, refs | die Eintrags-ID <baustein>/<name> (Dateiname ohne Nummer, Sprache und Endung), z. B. authors/jane; refs als YAML-Liste |
Für die Übersetzung gelten diese Regeln:
- Eine Übersetzungsdatei enthält nur Felder, die übersetzbar sind. Ein Zahlenfeld in der Datei
*.en.mdist ein Fehler. - Bei einem Bild gibt die Übersetzung nur
altan, nichtsrc. - Eine Liste in der Übersetzung darf nicht mehr Zeilen haben als die der Hauptsprache. Ginko ordnet die Zeilen nach ihrer Position zu.
- Übersetzungen sind immer aktuell. Es gibt kein „veraltet“. Fehlt eine Übersetzung, greift der Fallback aus dem Baustein (
untranslated).
Slug. Ohne Wert im Frontmatter nimmt Ginko den Dateinamen ohne Zahl, zum Beispiel einstieg für 1.einstieg.md. Eine Übersetzung darf mit slug: einen eigenen Slug setzen. Zwei Einträge mit gleichem Slug in einer Sprache sind ein Fehler.
Datendateien (YAML, JSON, CSV)
Ein Baustein ohne Markdown-Feld kann auch YAML-, JSON- und CSV-Dateien lesen. Nutze sie für strukturierte Inhalte ohne Fließtext, zum Beispiel eine Preisliste oder ein Team.
YAML und JSON. Eine Datei ist ein Eintrag, wie eine Markdown-Datei. Die ganze Datei ist das, was in Markdown das Frontmatter ist: dieselben Schlüssel, dieselben einfachen Formen, dieselben Regeln für Übersetzungen. Die Dateinamen folgen demselben Aufbau: content/menu/1.kaffee.yml, die Übersetzung content/menu/1.kaffee.en.yml, ein Einzelstück content/contact.json. In diesen Beispielen ist die Hauptsprache de, wie auf dieser Seite.
# content/menu/1.kaffee.yml
title: Kaffee
price: 3.5
vegan: trueDasselbe in JSON, als content/menu/2.tee.json:
{
"title": "Tee",
"price": 2.8,
"vegan": true
}CSV. Eine Datei enthält viele Einträge, einen pro Zeile. CSV gibt es nur für Sammlungen. Die erste Zeile nennt die Spalten, zum Beispiel in content/menu/preise.csv:
slug,locale,title,price,vegan
kaffee,,Kaffee,3.5,true
tee,,Tee,2.8,true
kaffee,en,Coffee,,- Die Spalte
slugist Pflicht. Sie ist der Name des Eintrags (die Id istmenu/kaffee) und sein Slug. - Die Spalte
localeist optional. Eine leere Zelle heißt Hauptsprache. Eine Zeile mitenist die Übersetzung der Zeile mit gleichem Slug. - Die anderen Spalten sind Feldnamen. Eine leere Zelle heißt „kein Wert“.
- Zahlen schreibst du mit Punkt:
3.5, nicht3,5. Ja/Nein-Werte sindtrueoderfalse. - Listen, Bilder,
choicesunddaterangepassen nicht in eine Zelle. Nimm dafür eine YAML- oder Markdown-Datei. - Das Trennzeichen ist ein Komma, oder ein Semikolon, wenn die erste Zeile mehr Semikolons hat (so speichert das deutsche Excel). Eine Zelle in doppelten Anführungszeichen darf das Trennzeichen, Zeilenumbrüche und
""für ein Anführungszeichen enthalten. - Die Zeilen behalten ihre Reihenfolge. Ein Ordner darf CSV-, YAML- und Markdown-Dateien mischen.
Entwürfe
draft: true im Frontmatter (oder in einer YAML- oder JSON-Datei, oder eine Spalte draft in CSV) hält einen Eintrag aus nuxt build, nuxt generate und ginko check heraus. Im Dev-Modus erscheint der Eintrag, und die Seite kann ihn mit entry.data.draft kennzeichnen:
---
title: Frühlingsfest
draft: true
---
Noch nicht fertig.<span v-if="'draft' in entry.data && entry.data.draft">Entwurf</span>Eine Sammlung, die ihre Einträge nach einem Datum absteigend ordnet (order: { field: 'date', dir: 'desc' }, wie posts()), plant Einträge auch: Ein Eintrag mit einem Datum nach dem Tag des Builds bleibt wie ein Entwurf draußen, und der erste Build an oder nach seinem Datum veröffentlicht ihn. Das gilt auch für nuxt build, denn Inhalte aus Dateien sind Teil des Builds: Damit ein Beitrag an seinem Tag erscheint, baue die Seite täglich neu (zum Beispiel mit einem zeitgesteuerten Deploy-Hook). Im Dev-Modus erscheinen geplante Einträge. Eine Liste ab dem ältesten Datum (Termine) betrifft das nicht: Ihre kommenden Termine sind gewollt.
Alte Adressen (redirectFrom)
Bekommt ein Eintrag einen neuen Slug, soll seine alte Adresse weiter funktionieren. redirectFrom im Frontmatter der Datei in der Hauptsprache nennt die alten Pfade. Jeder antwortet mit einer Weiterleitung 301 auf die Adresse des Eintrags:
---
title: Einstieg
redirectFrom:
- /docs/start
- /docs/first-steps
---Ein einzelner Pfad geht auch: redirectFrom: /docs/start. Jeder Pfad beginnt mit /. Weiterleitungen, die zu keinem Eintrag gehören, stehen in redirects in ginko.config.ts. Kommen alle Bausteine einer Seite aus Dateien, schreibt der Build die genauen Pfade auch als Route Rules, damit sie auf einem statischen Host funktionieren.
Markdown anzeigen
<GinkoMarkdown> zeigt den Wert eines f.markdown-Feldes. Das Ginko-Modul registriert die Komponente in jeder Seite.
<script setup lang="ts">
import Callout from './Callout.vue'
const route = useRoute()
const doc = useGinkoEntry('docs', () => route.params.slug)
</script>
<template>
<article>
<h1>{{ doc.data.title }}</h1>
<GinkoMarkdown :value="doc.data.body" :components="{ callout: Callout }" />
</article>
</template>Der Parser läuft beim Build. Zur Laufzeit gibt es keinen Parser. Code in Codeblöcken färbt Ginko mit Shiki ein. Code steht auf einer dunklen Fläche, die Farben kommen aus dem Theme catppuccin-mocha. Ginko kennt diese Sprachen: typescript, javascript, json, vue, bash, markdown, yaml, html und css. Andere Sprachen erscheinen ohne Farbe.
Komponenten. Im Markdown schreibst du eine Komponente als Block mit zwei Doppelpunkten:
::callout{type="warning"}
Ein Fehler in einer Datei bricht den Build ab.
::Ginko speichert den Block als Element callout. Die Seite entscheidet mit :components, welche Vue-Komponente dazu gehört. Fertige Komponenten wie callout, kbd oder badge bringt das Paket @lupinum/ginko-components mit. Wie du eine eigene hinzufügst, steht unter Eine eigene Komponente hinzufügen.
Inhaltsverzeichnis. Im Bundle einer Seite steht für ein Markdown-Feld nur ein Verweis. <GinkoMarkdown> holt den Text der aktuellen Seite nach. So enthält die Seite nur die Texte, die sie zeigt. Das Inhaltsverzeichnis toc kommt mit dem geladenen Text. Es enthält die Überschriften der Ebenen 2 und 3. Jeder Link hat id und text:
const tree = await useGinkoMarkdown(() => doc.value.data.body)
const toc = computed(() => tree.value?.toc?.links ?? [])Bilder
Ein Bild darf neben der Markdown-Datei liegen und wird mit einem relativen Pfad eingebunden. Ginko liest die Größe aus der Datei, legt das Bild unter einem Namen aus seinem Inhalt ab (/_ginko/img/<hash>.jpg, ein Jahr im Cache) und setzt width und height, damit die Seite beim Laden nicht springt.

Der Pfad muss unter content/ bleiben; fehlt die Datei, bricht der Build mit Datei und Pfad ab. Pfade mit / suchen unter public/ (nur die Größe wird ergänzt), https://-Bilder bleiben unverändert. Bildfelder rendert <GinkoImage :image="entry.data.hero" /> mit Größe und loading="lazy" (eager für das erste Bild der Seite).
Bildgrößen. Beim Build schreibt Ginko WebP-Kopien mit 480, 960 und 1600 Pixeln Breite, jede nur, wenn das Bild breiter ist. Markdown-Bilder und <GinkoImage> nennen sie in srcset, damit Handys eine kleine Datei laden. SVG- und GIF-Bilder bleiben, wie sie sind.
Bildformat. {aspect="4:3"} hinter einem Bild zeigt es in diesem Format: zugeschnitten um seinen Fokuspunkt (bei Bildern aus der Dateiablage des Studios, sonst um die Mitte) oder mit fit="contain" ganz, ohne Zuschnitt. Im Studio heißt das „Format“ und „Füllend / Eingepasst“ im Menü des Bildes.
{aspect="4:3"}Links zwischen Seiten
In Markdown verlinkst du einen Eintrag mit $<baustein>.<name> und eine Seite aus pages mit $page.<schlüssel>. Der Name ist die Id des Eintrags ohne den Baustein, zum Beispiel einstieg für docs/einstieg; sein Slug geht auch. Ginko setzt beim Build die Adresse der aktuellen Sprache ein, so bleibt der Link richtig, wenn sich ein Slug oder eine Route ändert. Ein Anker bleibt erhalten:
Siehe [Was Ginko ist]($docs.einstieg#was-ginko-ist) und [die Startseite]($page.home).Bei jedem Build und bei ginko check prüft Ginko die Links in allen Markdown-Feldern:
| Prüfung | Was sie findet | Standard |
|---|---|---|
broken | ein $…-Link zu einem Eintrag oder einer Seite, die es nicht gibt; ein /…-Pfad, zu dem kein Eintrag, keine Seite und keine Datei in public/ passt (auf Seiten, deren Bausteine alle aus Dateien kommen) | 'error' |
anchors | ein #anker, der auf der Zielseite keine Überschrift ist | 'warn' |
redirected | ein Link auf eine alte Adresse, die weiterleitet | 'warn' |
external | ein http(s)://-Link, dessen Server mit 404 oder 410 antwortet, dessen Host es nicht gibt oder der zweimal nicht antwortet | 'off' |
'error' bricht den Build ab, 'warn' gibt das Problem aus, 'off' lässt die Prüfung weg. Die Stufen setzt du mit linkCheck in ginko.config.ts:
export default defineSite({
// …
linkCheck: { anchors: 'error', external: 'warn' },
})Die Prüfung external fragt die Server über das Netz, bei nuxt build, nuxt generate und ginko check, nie im Dev-Modus. Mehr dazu in Plattform und CLI.
Fehler
Ein Fehler in einer Datei bricht den Build ab. Die Meldung beginnt mit [ginko] Errors in content/: und nennt Datei und Feld, bei einer CSV-Zeile oder einer unlesbaren Datei auch die Zeile. Zum Beispiel:
[ginko] Errors in content/:
- content/docs/1.einstieg.md (colour): Unknown field “colour”.
- content/menu/preise.csv:3 (price): Column “price”: “3,5” is not a number. Use a point as the decimal separator (12.5, not 12,5).Die Meldungen sind englisch, wie alle Meldungen für Entwickler. Das sind Beispiele:
- ein Schlüssel im Frontmatter, den der Baustein nicht kennt
- ein Sprachkürzel im Dateinamen, das die Seite nicht hat
- eine Übersetzung ohne Datei der Hauptsprache
- zwei Einträge mit gleichem Slug in einer Sprache
- ein Bild oder eine Datei, die unter
public/fehlt - ein Wert, den die Regeln des Feldes ablehnen, zum Beispiel ein zu langer Titel
- ein Text unter dem Frontmatter, obwohl der Baustein kein Markdown-Feld hat
- eine YAML-, JSON- oder CSV-Datei, die sich nicht lesen lässt, oder eine CSV-Zelle, die nicht zu ihrem Feld passt
- in einem Block
```md previewMarkdown, das das Feld nicht erlaubt, zum Beispiel::scriptoder einjavascript:-Link
[ginko] Errors in content/ (these files are missing from the site):) und lässt die Datei weg. Prüfe darum das Log, wenn eine Seite fehlt.