Tippe, um Seiten und Abschnitte zu finden.

Zum Inhalt
Ginko

Einstieg

Was Ginko ist, wann du es nutzt und wie du in fünf Minuten eine erste Seite baust.

Was Ginko ist

Ginko verbindet eine Nuxt-Seite mit ihren Inhalten. Die Seite bleibt normaler Nuxt-Code. Die Inhalte kommen aus einem Bundle. Ein Bundle ist eine JSON-Antwort mit allen Inhalten der Seite in einer Sprache.

Die Inhalte kommen aus zwei Quellen:

  • Plattform. Inhaber pflegen Inhalte auf der gehosteten Ginko-Plattform, zum Beispiel Öffnungszeiten, Speisekarte oder Veranstaltungen. Die Seite holt das Bundle von dort.
  • Dateien. Entwickler legen Texte als Markdown-Dateien ins Repo, zum Beispiel diese Doku oder Rechtstexte. Beim Build entsteht daraus dasselbe Bundle.

Eine Seite darf beide Quellen mischen. Die Seite liest beide gleich, mit useGinko.

Ginko kennt vier Begriffe:

  • Baustein (englisch brick): ein Inhaltstyp, zum Beispiel „Speisekarte“. Du beschreibst ihn in ginko.config.ts.
  • Feld: ein Wert in einem Baustein, zum Beispiel ein Titel oder ein Preis.
  • Eintrag: ein Datensatz eines Bausteins, zum Beispiel ein Gericht.
  • Bundle: alle Bausteine mit ihren Werten, wie die Seite sie liest.

Wann du Ginko nutzt

Nutze Ginko, wenn eine Seite Inhalte hat, die jemand regelmäßig ändert, und dieser Jemand keinen Code anfassen soll. Beispiele: Öffnungszeiten, ein Mittagsmenü, Veranstaltungen, Stellenanzeigen.

Nutze die Datei-Quelle für Texte, die Entwickler pflegen: Doku, Blog, Rechtstexte.

Ginko ist kein Seitenbaukasten. Du legst Bausteine und Felder fest. Das Layout bleibt Code in deiner Nuxt-Seite. Für eine Seite ohne wechselnde Inhalte brauchst du Ginko nicht.

Deine erste Seite in fünf Minuten

Die erste Seite nutzt die Datei-Quelle. Sie braucht keine Plattform und läuft sofort. Du brauchst Node.js 22.18 oder neuer (22, 24 oder 26) und npm, pnpm oder bun.

Der schnellste Weg ist der Starter:

Terminal
npm create ginko@latest meine-seite -- --locale de
cd meine-seite
npm install
npm run dev

Öffne http://localhost:3000. Die Seite zeigt den Beispieltext aus content/start.md.

--locale de macht den Starter deutsch; ohne die Option ist er englisch. Schreib die Optionen nach --, sonst behält npm sie für sich. Weitere Starter:

Terminal
npm create ginko@latest mein-gasthaus -- --preset gastro --locale de
npm create ginko@latest meine-doku -- --preset docs --locale de

--preset gastro startet eine Seite für ein Gasthaus mit Öffnungszeiten und Speisekarte (siehe „Presets“). --preset docs startet eine Doku-Seite. Der Befehl sagt immer, welchen Starter er genommen hat.

Die Schritte unten bauen dieselbe Seite von Hand und erklären jede Datei, die der Starter anlegt.

1. Paket anlegen

package.json
{
  "name": "meine-seite",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare"
  },
  "dependencies": {
    "@lupinum/ginko": "^0.4.0",
    "@lupinum/ginko-content": "^0.4.0",
    "nuxt": "^4.6.0",
    "vue": "^3.5.0"
  }
}

Das Paket @lupinum/ginko-content liest die Markdown-Dateien. Du brauchst es nur für Bausteine mit source: 'files'.

Führe danach npm install aus. Mit pnpm 11 legst du vorher eine pnpm-workspace.yaml an, mit allowBuilds: und darunter esbuild: true. Sonst bricht pnpm ab, weil esbuild sein Programm in einem Install-Skript lädt. Der Starter hat diese Datei schon.

2. Bausteine beschreiben

ts
// ginko.config.ts
import { defineSite, f, single } from '@lupinum/ginko'

export default defineSite({
  site: 'meine-seite',          // Kennung: Kleinbuchstaben, Ziffern, Bindestriche
  name: 'meine-seite',          // der Name der Seite für Menschen
  url: 'https://example.com',   // Adresse der fertigen Seite
  timeZone: 'Europe/Vienna',
  locales: ['de'],              // die erste Sprache ist die Hauptsprache
  currency: 'EUR',
  pages: { home: '/' },         // feste Seiten: Schlüssel und Pfad
  bricks: {
    start: single({
      label: 'Startseite',
      source: 'files',
      fields: {
        title: f.text('Titel'),
        body: f.markdown('Text', { profile: 'basic' }),
      },
    }),
  },
})

single beschreibt einen Baustein mit genau einem Eintrag. Die Felder bekommen ein Label und optional Einstellungen. Das Profil basic erlaubt im Text Absätze, Überschriften, Listen, Zitate und Links, aber keine Bilder und Tabellen. Mehr dazu auf der Seite „Bausteine“.

3. Das Nuxt-Modul einschalten

ts
// nuxt.config.ts
import ginko from './ginko.config.ts'

export default defineNuxtConfig({
  compatibilityDate: '2026-10-01',
  modules: ['@lupinum/ginko/nuxt'],
  ginko,
})

Das Modul prüft die Konfiguration beim Start. Ein Fehler darin bricht den Build ab. Auch ein unbekannter Schlüssel, zum Beispiel redirect statt redirects, ist ein Fehler.

4. Eine Seite schreiben, die useGinko liest

vue
<!-- app/app.vue -->
<template>
  <NuxtPage />
</template>
vue
<!-- app/pages/index.vue -->
<script setup lang="ts">
const start = useGinko('start')
</script>

<template>
  <main>
    <h1>{{ start.data.title }}</h1>
    <GinkoMarkdown :value="start.data.body" />
  </main>
</template>

useGinko('start') liefert für ein Einzelstück data mit den Feldern. Die Typen kommen aus deiner ginko.config.ts. Das Modul importiert useGinko und GinkoMarkdown selbst.

5. Inhalt als Datei anlegen

markdown
<!-- content/start.md -->
---
title: Hallo Ginko
---

Dieser Text liegt in `content/start.md`. Ändere ihn, und die Seite zeigt den neuen Text.

Der Name der Datei ist der Name des Bausteins. Das Frontmatter füllt die Felder, der Text darunter füllt das Markdown-Feld.

Starte die Seite mit npm run dev. Die Seite zeigt „Hallo Ginko“. Im Dev-Modus liest Ginko geänderte Dateien in content/ neu ein.

Eine Seite mit Plattform-Bausteinen liest dieselben Composables. Du lässt source: 'files' weg, gibst den Pflichtfeldern des Singles einen default oder machst sie freiwillig (f.markdown, f.image und f.file können keinen default haben, darum setzt du bei ihnen optional: true) und setzt platform in der ginko.config.ts auf die Adresse der Plattform. Mehr dazu auf den Seiten „Seiten lesen“ und „Plattform und CLI“.

Um eine Datei-Seite ohne Codeänderung von der Plattform zu betreiben, etwa die Kopie einer Vorlage in einer Demo, setzt du GINKO_CONTENT=platform für den Build und für ginko release. Dann liest jeder Baustein von der Plattform, als stünde source: 'files' nicht da.

Weiter: eine Sammlung und eine zweite Sprache

Die erste Seite hat ein Single in einer Sprache. Die meisten Seiten brauchen mehr: eine Liste von Einträgen mit eigenen Seiten und eine zweite Sprache. Dieser Abschnitt baut beides in die Seite von oben ein: Neuigkeiten auf Deutsch und Englisch.

1. Sprache und Sammlung ergänzen

ts
// ginko.config.ts
import { collection, defineSite, f, single } from '@lupinum/ginko'

export default defineSite({
  site: 'meine-seite',
  name: 'meine-seite',
  url: 'https://example.com',
  timeZone: 'Europe/Vienna',
  locales: ['de', 'en'],                       // de bleibt die Hauptsprache
  currency: 'EUR',
  pages: { home: { de: '/', en: '/en' } },     // ein Pfad pro Sprache
  bricks: {
    start: single({
      label: 'Startseite',
      source: 'files',
      fields: {
        title: f.text('Titel'),
        body: f.markdown('Text', { profile: 'basic' }),
      },
    }),
    news: collection({
      label: 'Neuigkeiten',
      source: 'files',
      max: 100,                                 // höchstens so viele Einträge (bis 500)
      order: { field: 'date', dir: 'desc' },   // die neueste zuerst
      route: { de: '/neuigkeiten/[slug]', en: '/en/news/[slug]' },
      fields: {
        title: f.text('Titel'),
        date: f.date('Datum'),
        slug: f.slug('Adresse', { from: 'title' }),
        body: f.markdown('Text'),
      },
    }),
  },
})

npm run dev startet von selbst neu, wenn sich ginko.config.ts ändert. Eine collection hat viele Einträge. route gibt jedem Eintrag eine eigene Seite. Der Pfad jeder Sprache enthält schon ihr Präfix und braucht [slug]. Das Slug-Feld muss slug heißen.

2. Einträge und Übersetzungen schreiben

text
content/
  start.md
  start.en.md            # englische Fassung der Startseite
  news/
    eroeffnung.md        # ein Eintrag, Hauptsprache
    eroeffnung.en.md     # derselbe Eintrag auf Englisch
markdown
<!-- content/news/eroeffnung.md -->
---
title: Wir haben geöffnet
date: 2026-10-12
---

Die neue Seite ist online.
markdown
<!-- content/news/eroeffnung.en.md -->
---
title: We are open
slug: we-are-open
---

The new site is live.

Eine Übersetzung hat denselben Dateinamen plus den Sprachcode. Sie enthält nur übersetzbare Felder, darum bleibt das Datum in eroeffnung.md. Ohne slug: kommt der Slug aus dem Dateinamen: eroeffnung. Schreib start.en.md genauso, mit englischem title und Text.

3. Die Seite für einen Eintrag anlegen

vue
<!-- app/pages/neuigkeiten/[slug].vue -->
<script setup lang="ts">
definePageMeta({ i18n: { paths: { en: '/news/:slug()' } } })
const route = useRoute()
const post = useGinkoEntry('news', () => route.params.slug)
</script>

<template>
  <article>
    <h1>{{ post.data.title }}</h1>
    <GinkoMarkdown :value="post.data.body" />
  </article>
</template>

Die Datei legt den Pfad der Hauptsprache fest. definePageMeta legt den englischen Pfad fest; das Präfix /en ergänzt ginko-i18n selbst. Ein falscher Slug ergibt ein echtes 404.

4. Einträge auflisten und die Sprache wechseln

vue
<!-- app/pages/index.vue -->
<script setup lang="ts">
const start = useGinko('start')
const news = useGinko('news')
</script>

<template>
  <main>
    <h1>{{ start.data.title }}</h1>
    <GinkoMarkdown :value="start.data.body" />
    <ul>
      <li v-for="item in news.items" :key="item.id">
        <NuxtLink :to="item.url">{{ item.data.title }}</NuxtLink>
      </li>
    </ul>
  </main>
</template>
vue
<!-- app/app.vue -->
<script setup lang="ts">
const { alternates } = useI18n()
</script>

<template>
  <nav aria-label="Sprache">
    <NuxtLink v-for="a in alternates" :key="a.code" :to="a.path" :hreflang="a.language" :aria-current="a.current ? 'page' : undefined">
      {{ a.code.toUpperCase() }}
    </NuxtLink>
  </nav>
  <NuxtPage />
</template>

Jeder Eintrag trägt in url seine Adresse in der aktuellen Sprache und in urls seine Adresse in jeder Sprache, in der er übersetzt ist. Steht ein unübersetzter Eintrag mit untranslated: 'fallback' in der Liste, zeigt url auf seine Seite in der Hauptsprache. Nutze url für Links auf Einträge, keinen selbst gebauten Pfad. alternates ist die aktuelle Seite in jeder Sprache. Der Umschalter auf /neuigkeiten/eroeffnung führt also zu /en/news/we-are-open.

Öffne http://localhost:3000 und http://localhost:3000/en. Beide Seiten listen die Neuigkeit in ihrer Sprache. Führe vor einem Build npx ginko check aus. Es prüft die Konfiguration, die Inhaltsdateien und die Links zwischen ihnen.

Wie es weitergeht

  • „Bausteine“ erklärt Felder, Routen, Sichtbarkeit und Reihenfolge.
  • „Seiten lesen“ zeigt alle Composables und Komponenten.
  • „Sprachen“ richtet mehrere Sprachen ein.
  • „Inhalte aus Dateien“ beschreibt Markdown-Bausteine im Detail.
  • „Plattform und CLI“ erklärt Release und Backup.