Type to search pages and sections.

Skip to content
Ginko

Reading content

How a site reads content. useGinko, useGinkoEntry, useGinkoToday, GinkoMarkdown, formatMoney, fallback, errors, and a reference of every function.

useGinko

useGinko(brick) reads one brick. It returns a ComputedRef. In <script> you use .value; in the template you do not.

  • For a single, it returns { data, fallback }. data holds the fields.
  • For a collection, it returns { items }. Each item has id, data and fallback. An item of a brick with a route also has url, its address in the current locale (use it for links), and urls, its address in every locale it is translated into (for a language switcher). For an entry listed through untranslated: 'fallback' in a locale without its translation, url is the main-language page. updatedAt is the time of the last change (ISO 8601, UTC) when it is known.
vue
<script setup lang="ts">
import { formatMoney } from '@lupinum/ginko'

const menu = useGinko('menu')
const { language } = useI18n()
</script>

<template>
  <ul>
    <li v-for="item in menu.items" :key="item.id">
      {{ item.data.name }}: {{ formatMoney(item.data.price, language) }}
    </li>
  </ul>
</template>

useI18n() comes from ginko-i18n. It returns the language tag of the current locale (such as de-AT) as language. useGinko is imported automatically. formatMoney is not: import it from @lupinum/ginko.

The types come from your ginko.config.ts. There, item.data.price is an f.money field, so it is { amount, currency }. An optional field can be undefined.

For a collection, useGinko returns only visible entries, in the order of the brick. The rule is in "Bricks", section "Visibility".

When a component gets an entry as a prop, use the type GinkoItem with the name of the brick. It is imported automatically:

vue
<script setup lang="ts">
defineProps<{ dish: GinkoItem<'menu'> }>()
</script>

useGinkoEntry

useGinkoEntry(brick, slug) reads one entry of a collection that has a route. You pass the slug from the address as a function, so the entry follows the route when Nuxt reuses the page for another entry. The file lives, for example, at app/pages/veranstaltungen/[slug].vue:

vue
<script setup lang="ts">
definePageMeta({ i18n: { paths: { en: '/events/:slug()' } } })
const route = useRoute()
const event = useGinkoEntry('events', () => route.params.slug)
</script>

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

useGinkoEntry builds the path from the route of the brick and the slug. A slug list (from a catch-all page such as [...slug].vue) is joined with /. Then it looks for the visible entry whose address in the current locale is exactly this path. If it finds none, the site answers with a real 404. This happens for a wrong or missing slug, for an entry that is not visible, for an entry that is not translated in this locale, and for the slug of another locale. The 404 is thrown on every read of the entry (in the template or with .value) while no entry matches, not at the call.

useGinkoQuery

useGinkoQuery(brick, query) filters, sorts and pages the visible entries of a collection. It reads the data of the page, not a database:

ts
const mains = useGinkoQuery('menu', { where: { category: 'mains', price: { lte: { amount: 1500 } } }, order: 'price desc, name', limit: 10 })

where takes per field a value (the field must equal it) or a condition. Every field must match:

  • eq, ne: equal or not equal. For a list value (f.choices, f.refs), one element counts.
  • gt, gte, lt, lte: greater or smaller. Money compares by amount, a daterange by its start date, dates and times as text.
  • in: equal to one of the values in the list.
  • contains: a text contains the text (upper and lower case count the same), a list contains the value.

order is a text such as 'price desc, name' (asc is the default) or { field, dir? }. limit must be at least 1; page starts at 1. fields keeps only these fields, facets counts the values of these fields over all matches, and include replaces the IDs of these ref fields with the entries. The types come from your config: a field the brick does not have is a type error in your code editor and an error when the query runs.

useGinkoToday

useGinkoToday() returns today's date as YYYY-MM-DD, in the time zone of the site. The value is a Ref. On the server, the server's value comes first, so that server and browser render the same day. In the browser, the value moves on after midnight: right after load, every minute, and when the visitor returns to the tab. useGinko filters with this value. So expired notices disappear without a reload.

Use useGinkoToday() for your own date checks too, for example "open today".

useGinkoSite

useGinkoSite() returns the facts of the site from ginko.config.ts: name, url (the production address), locales, mainLocale (the first locale) and timeZone. Use it for absolute links, for example in JSON-LD:

ts
const { url } = useGinkoSite()
const address = new URL(useRoute().path, url).href

The module writes more values to runtimeConfig.public.ginko. They are internal and can change in any release, so read the site only through useGinkoSite().

GinkoMarkdown

<GinkoMarkdown :value> shows the value of an f.markdown field:

vue
<GinkoMarkdown :value="legal.data.imprint" class="prose" />

The text is stored as Markdown and the bundle delivers it as a tree. No parser runs in the browser and nothing uses v-html. An unsafe link (for example javascript:) becomes plain text, and an attribute the parser does not allow (for example style) is left out. With :components you map tags to your own components; a component keeps the props that components in ginko.config.ts declares for it. See "Content from files" for more.

useGinkoSeo

useGinkoSeo sets the head of a page: title (with seo.titleTemplate of the site), description, Open Graph and Twitter tags, and JSON-LD. ginko-i18n sets the canonical link and hreflang (see Languages). Call it once per page, with a function so the values follow the content:

ts
const route = useRoute()
const event = useGinkoEntry('events', () => route.params.slug)
useGinkoSeo(() => ({
  title: event.value.data.title,
  image: event.value.data.image?.url,
  type: 'article',
}))
OptionMeaning
title, descriptionDefaults: the site name and seo.description
imageA path under public/ or an absolute URL, or an object { url, width?, height?, alt? } (SeoImage) that also gives the size and alt text. Default: seo.image. With generated share cards, the image goes into the card.
type'website' (default) or 'article'
sectionThe small label above the title on the generated share card, for example the section of a docs page. Without a generated card it does nothing.
noindexKeeps the page out of search engines
jsonLdExtra JSON-LD objects. SEO and agents has an example.

Share cards. Sites from create-ginko come with nuxt-og-image and its renderer @takumi-rs/core. Then every page gets a generated card for links in chats and social networks: the site name and logo (seo.organization.logo), the title, the description, the section and the page's image, on the brand colour seo.color. On a light brand colour the text is dark. To add it to an existing site (Nuxt 4.6.1 or later), install nuxt-og-image and @takumi-rs/core and add 'nuxt-og-image' to modules. A site that renders on a server needs NUXT_APP_SECRET (see Publishing). With the module, the card values are read once, when useGinkoSeo runs: call it after the page's data is loaded. Without the module, og:image is the page's image or seo.image, and Ginko loads nothing of nuxt-og-image.

formatMoney

formatMoney(money, language) turns a money value into text. language is a language tag such as de-AT, not the locale code: in a page, pass useI18n().language. Import it from @lupinum/ginko.

CallResult
formatMoney({ amount: 1250, currency: 'EUR' }, 'de')12,50 €
formatMoney({ amount: 1250, currency: 'EUR' }, 'en')€12.50
formatMoney({ amount: 1250, currency: 'CHF' }, 'de')12,50 CHF
formatMoney({ amount: 1250, currency: 'CHF' }, 'de-CH')CHF 12.50

Every space in the result is a no-break space. So the price does not wrap, and server and browser show the same text.

Fallback and the lang attribute

If a field has no translation in the current locale, the bundle shows the text of the main locale. An outdated translation, whose main text changed after it, stays as it is until someone translates it again or confirms it; the Studio marks it. In a brick with a route, though, an entry with a missing required text is left out of that locale (see Languages). The list fallback names each such field by its path. For a field in a list row, the path is the field, the row _id, and the column, for example days.k3Fd9aZ0qx.dish.

A screen reader pronounces foreign text wrongly if the site does not mark it. So set lang to the main locale when the path is in fallback.

Ginko ships useGinkoLang() for this (auto-imported). It returns a function (item, path). The function gives the language tag of the main locale, for example de-AT from languages in ginko.config.ts, when the field comes from the fallback, otherwise undefined:

vue
<script setup lang="ts">
const menu = useGinko('menu')
const langOf = useGinkoLang()
</script>

<template>
  <h3 v-for="item in menu.items" :key="item.id" :lang="langOf(item, 'name')">{{ item.data.name }}</h3>
</template>

For a list row of a single, give the full path:

vue
<span :lang="langOf(lunch, `days.${day._id}.dish`)">{{ day.dish }}</span>

For an image, the path of the alt text is image.alt if the field is called image. A value of undefined leaves the attribute out.

ginko-i18n sets the lang attribute on the <html> element for you. See "Languages".

Errors and cache

The site loads the bundle once per request. If that fails (network, server error, wrong format, too many requests), the site answers with 503 and never with 404. On Vercel, a 503 keeps the old, good page in the cache; a 404 would replace it. Only an entry that is confirmed missing returns 404. The module turns on ISR for all pages (by default expiration: 60, and query parameters share one cache entry). Your own server routes under /api/** and Ginko's dynamic routes under /__ginko/ run without cache (details: Publishing). A site whose bricks all come from files never asks the platform, so the platform cannot cause a 503.

Do not read useGinko or any other composable that needs the bundle in error.vue. On a 503 there is no bundle, and the error page would fail itself.

API reference

Auto-imports

The Nuxt module imports these functions in every component, page and composable of your site. brick is the key of a brick in ginko.config.ts. The types follow from your configuration.

The rule for the names: a function that needs the Nuxt context starts with useGinko. Call it in setup or in another composable, not in an event handler. A function that needs no Nuxt context is not auto-imported; you import it from @lupinum/ginko (see below). The one exception is fetchGinkoMarkdown: it needs the site's own routes but no Nuxt context, so you can call it anywhere in the site.

FunctionReturnsUse
useGinko(brick)ComputedRef<{ data, fallback }> for a single, ComputedRef<{ items }> for a collectionRead one brick. Collections hold only visible entries.
useGinkoEntry(brick, slug: MaybeRefOrGetter<string | string[] | undefined>)Readonly<Ref<GinkoItem<brick>>>The visible entry with this slug in the current locale, else a 404. See useGinkoEntry.
useGinkoQuery(brick, query?: MaybeRefOrGetter<Query>)ComputedRef<{ items, total, page, pages, facets? }>Filter, sort and page the visible entries. query has where, order, limit, page (from 1), fields, facets and include. See useGinkoQuery.
useGinkoNeighbors(brick, id: MaybeRefOrGetter<string>, options?: { order?: Query['order'] })ComputedRef<{ prev?, next? }>The entries before and after id, for "previous" and "next" links. order is the same as in Query (for example { order: 'date desc' }); without it the order of the brick applies.
useGinkoNav(bricks: MaybeRefOrGetter<string[]>)ComputedRef<NavNode[]>A navigation tree of the visible entries of these collections.
useGinkoBreadcrumbs(bricks: MaybeRefOrGetter<string[]>)ComputedRef<NavNode[]>The nodes from the root to the current page. Empty if the page is not in the tree.
useGinkoToday()Ref<string>Today as YYYY-MM-DD in the time zone of the site.
useGinkoWeek()WeekInfoThe first day of the week and the weekend days of the site.
useGinkoLocale()ComputedRef<GinkoLocale>The current locale.
useGinkoPath()(key: GinkoPageKey, locale?: GinkoLocale) => stringA function that gives the path of a page from pages, in the current or another locale.
useGinkoLang()(item, path: string) => string | undefinedA function that gives the lang value for a field from the fallback. See Fallback and the lang attribute.
useGinkoSite(){ name, url, locales, mainLocale, timeZone }The facts of the site. See useGinkoSite.
useGinkoSeo(input: MaybeRefOrGetter<GinkoSeoInput>)voidThe head of a page. See useGinkoSeo.
useGinkoMarkdown(value: MaybeRefOrGetter<MarkdownValue | undefined>)Promise<ComputedRef<MarkdownTree | null>>The tree of a Markdown field, to split or read it in code. Use it with await.
fetchGinkoMarkdown(value: MarkdownValue | undefined)Promise<MarkdownTree | null>The same for server code, for example inside useAsyncData. Put the locale in its key, such as () => `services:${locale.value}`, or a language switch keeps the old texts.

These types are imported automatically too:

TypeMeaning
GinkoItem<'brick'>One entry of a collection, for component props: defineProps<{ dish: GinkoItem<'menu'> }>()
GinkoPageKeyA key of pages
GinkoLocaleA locale of the site
GinkoSeoInputThe argument of useGinkoSeo

Server routes

Your own server routes (server/routes/, server/middleware/) get one more auto-import:

FunctionReturnsUse
getGinkoItems(event, brick, locale?)Promise<GinkoServerItem[]>The visible entries of a collection in a locale (default: the main one), in the brick's order, each with url when the collection has a route: what useGinko(brick).items holds on a page. Drafts and entries past their visible date are left out. A locale the site does not have or a key that is no collection answers 404; an unreachable platform 503. For a feed or a calendar file, for example with rssXml from @lupinum/ginko/basics.

Components

ComponentProps
<GinkoMarkdown>value (a Markdown field), components (your own components by tag). See GinkoMarkdown.
<GinkoImage>image (an image field), sizes (default 100vw when the image has smaller copies, variants), eager (load at once, for the main image of a page), frame (the field's named frame; a field with one ratio needs none, original shows the whole photo). It writes srcset, width, height and alt; with a frame also aspect-ratio, object-fit: cover and the focus point, with height in the frame's shape.

@lupinum/ginko

You import these names yourself:

NameUse
defineSite, single, collection, fThe configuration. See Bricks.
formatMoney(money, language)Money as text. Not auto-imported: import it where you use it.
safeHref(href: string | undefined)The URL if it is safe for href or src, else undefined. Safe are http, https, mailto, tel, a path of the site (/…), a fragment (#…) and a route link ($…). Use it for links and images from content.
plainText(tree: MarkdownTree)The text of a Markdown tree without formatting, for example for a meta description.
wordsOf(value: MarkdownValue)The number of words of a Markdown value, for example for a reading time. Works in lists too, where the text is not loaded.
breadcrumbJsonLd(steps, siteUrl)A JSON-LD BreadcrumbList from steps with title and path (such as the nodes of useGinkoBreadcrumbs); siteUrl from useGinkoSite() makes the paths absolute. See SEO and agents.
startOfWeek(date: string, week: WeekInfo)The first day of the week that contains date, as YYYY-MM-DD. Pass useGinkoWeek() as week.
defineComponents(registry)Defines your own Markdown components (tag → definition) for components in the site config. See Components.

It also exports these types. Each one comes from your config or from a function above:

TypeWhat it is
SiteInput, Migration, MigrationContextThe site config, one migration (brick, fills, compute) and what compute gets.
Field, Fields, Brick, SingleBrick, CollectionBrickFields and bricks in their stored form (with pos and retired, which the platform sets). Use them to type your own brick factories.
Task, Visibility, BrickSource, RouteTemplate, ChoiceOptionParts of a brick: one task, visible, source, one value of route, one option of f.choice.
LinkConfig, Redirect, ComponentRegistry, ComponentDef, MarkdownProfileConfig parts you may build in separate files: linkCheck, one of redirects, components and one component, a Markdown profile.
Locale, Currency, Label, JsonA locale code (de, pt-BR): the key of translated texts and the URL prefix, not the language tag. An ISO 4217 code (EUR), a text or one text per language, any JSON value.
DataOf, ItemOfThe data of a brick's fields, and one entry of a collection, typed from your config.
Money, ImageValue, FileValue, UnknownChoiceValues in the bundle. UnknownChoice is a choice value a later release added; your code needs a fallback for it.
MarkdownValue, MarkdownTree, MarkdownToc, TocLinkThe value of a Markdown field, its tree, its table of contents and one link in it.
NavNodeOne node of useGinkoNav.
Query, ConditionThe query of useGinkoQuery and the condition on one field.
WeekInfoFirst day of the week and weekend days, from useGinkoWeek.
SeoImageThe object form of image in useGinkoSeo: { url, width?, height?, alt? }.

@lupinum/ginko/vue exports GinkoMarkdown for a Vue app without Nuxt. It has the same name as the Nuxt component, but a smaller contract: value must be a MarkdownTree (or null), because it loads no texts; and it uses only the components you pass in components, none that a module provides (such as @lupinum/ginko-components). Pass your component registry (from defineComponents) as registry: a component keeps only the props it declares there, and without it only title, lang, dir and aria-*. It renders at once and needs no <Suspense>. The Nuxt <GinkoMarkdown> is asynchronous, which Nuxt pages handle by themselves.

@lupinum/ginko/search reads the search index of a site: loadIndex, search and the types SearchHit, SearchIndex and SearchIndexJson. loadIndex is async: it also loads the word rules (stemmer) of the index's language, and only that one. SEO and agents has an example.

These entry points are the public API. @lupinum/ginko/internal and @lupinum/ginko/internal/search are for Ginko's own packages and can change in any release.