Skip to main content

Composables

Route-aware page loading and headless content search.

Ginko exposes two public composables: useContentPage() and useContentSearch(). Import them from @lupinum/ginko-content/client. The Nuxt module also auto-imports useContentPage and the collision-safe search name useGinkoContentSearch.

useContentPage()

Resolve the current Nuxt route against one collection.

ts
useContentPage(handle, options?): Promise<UseContentPageResult>
app/pages/docs/[...slug].vue
<script setup lang="ts">
import { createError } from '#imports'
import { useContentPage } from '@lupinum/ginko-content/client'
import { docs } from '~~/content.config'

definePageMeta({ key: route => route.path })

const { page, status, error } = await useContentPage(docs, {
  fallback: true
})

if (!page.value) {
  throw createError({ statusCode: 404, statusMessage: 'Document not found', fatal: true })
}
</script>

<template>
  <ContentRenderer v-if="page" :value="page" />
</template>

The page key reruns setup when the path changes, without remounting for query or hash changes. The composable integrates with the Nuxt SSR payload and hides a previous route's document while the next route is resolving. fatal: true makes the same route-owned 404 policy reach Nuxt's error page during client navigation.

Options

Options may contain refs or getters.

OptionTypeDefaultPurpose
localestringCurrent route localeOverride locale inference
fallbackLocaleFallbackexactResolve a configured or explicit locale fallback
selectstring[]all fieldsProject authored fields
populatePopulateSpecnoneResolve reference fields
surroundboolean | { select? }falseLoad previous and next navigation items

Setting surround performs one additional request. Pass true for the default projection or an object to select fields:

ts
const { page, previous, next } = await useContentPage(docs, {
  surround: { select: ['description', 'icon'] }
})

Return value

FieldTypePurpose
pageComputedRef<LocalizedContentDocument | undefined>Current matching document
previousComputedRef<NavigationItem | null>Previous item when surround is enabled
nextComputedRef<NavigationItem | null>Next item when surround is enabled
statusComputedRef<string>Nuxt async-data status
errorComputedRef<unknown>Page or surround request error
refresh()() => Promise<void>Refresh the page and optional surround request

Returned documents use the document envelope. useContentPage() does not throw a 404, update <head>, or redirect from a fallback URL; the route component owns those policies.

useContentSearch()

Create headless search state for a combobox, modal, or command palette.

ts
useContentSearch(options?): Promise<UseContentSearchResult>
ts
import { useContentSearch } from '@lupinum/ginko-content/client'

const search = await useContentSearch({
  initialQuery: '',
  limit: 8,
  locale: computed(() => 'en')
})

search.setQuery('routing')
const selected = search.select(0)

The configured content.search.engine supplies results. Passing a collection additionally loads that collection's search sections and search-shaped navigation:

ts
import { docs } from '~~/content.config'

const { files, searchNavigation, results } = await useContentSearch({
  collection: docs,
  locale: computed(() => 'en')
})

Keep this call outside <ClientOnly> when server-rendered search UI needs files or searchNavigation in its payload.

Options

OptionTypeDefaultPurpose
initialQueryMaybeRefOrGetter<string>''Initial writable query value
limitMaybeRefOrGetter<number>engine result countLimit exposed results
localeMaybeRefOrGetter<string>noneFilter results and collection data
collectionHandle or dynamic namenoneAlso load search sections and search navigation

Return value

FieldPurpose
queryWritable search term ref
resultsNormalized, limited search results
pending / errorBackend loading state
activeIndex / activeResultCurrent highlighted result
hasQuery / hasResults / isEmptyDerived rendering state
setQuery(value) / reset()Query controls
next() / previous() / setActiveIndex(index)Highlight controls
select(index?)Return a result or null without navigating
filesCollection search sections; empty without collection
searchNavigationSearch-shaped collection navigation; empty without collection

Search errors are exposed through error, including disabled search, provider failures, and Pagefind loading failures. Each result contains collection and path, so routing does not need to infer collection identity from URL prefixes.

Other client exports

These are ordinary functions, not composables. getCollectionPath(), findFirstNavigationPage(), and extractContentToc() are framework-free derivations. querySiteData() is a one-shot async Nuxt query function:

ExportSignaturePurpose
getCollectionPath(collection, options?) => stringProject a collection route from locale, slug, or path; localePrefix: false omits the application locale prefix
findFirstNavigationPage(items?) => item | nullFind the first route-bearing page depth-first, skipping structural nodes
querySiteData(key, options?) => Promise<{ key, locale?, data, updatedAt? }>Read provider site data; options are locale and an optional custom fetcher
extractContentToc(content, options?) => TocDerive an H2–H4 table of contents from a Markdown string

getCollectionPath() options are locale, slug, path, and localePrefix; path takes precedence over slug. Locale mounts come from the collection handle and cannot be overridden per call. A handle using inherited i18n: true does not contain that inherited policy, so this pure helper requires the explicit collection-local i18n: { locales, defaultLocale } form instead of guessing. extractContentToc() accepts depth, title, and searchDepth, with a maximum heading depth of 4 by default.

For lists, pagination, navigation, backlinks, and exact document reads, compose the one-shot async Nuxt query functions with useAsyncData(). That composition gives Nuxt ownership of SSR payload transfer, cache keys, reactive state, pending state, and errors. Pre-v3 query composable names were removed rather than deprecated; the Ginko version migration maps those names to their replacements.