Composables
Route-aware page loading and headless content search.
Ginko exposes useContentPage(), useContentLocalePath(), and useContentSearch(). Import them from @lupinum/ginko-content/client. The Nuxt module also auto-imports the page and locale-link functions and the collision-safe search name useGinkoContentSearch.
useContentPage()
Resolve the current Nuxt route against one collection.
useContentPage(handle, options?): Promise<UseContentPageResult><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 (status.value === 'not-found') {
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.
| Option | Type | Default | Purpose |
|---|---|---|---|
locale | string | Current route locale | Override locale inference |
fallback | LocaleFallback | exact | Resolve a configured or explicit locale fallback |
select | string[] | all fields | Project authored fields |
populate | PopulateSpec | none | Resolve reference fields |
surround | boolean | { select? } | false | Load previous and next navigation items |
Setting surround performs one additional request. Pass true for the default projection or an object to select fields:
const { page, previous, next } = await useContentPage(docs, {
surround: { select: ['description', 'icon'] }
})Return value
| Field | Type | Purpose |
|---|---|---|
page | ComputedRef<LocalizedContentDocument | null> | Current matching document, or null when it is not available |
previous | ComputedRef<NavigationItem | null> | Previous item when surround is enabled |
next | ComputedRef<NavigationItem | null> | Next item when surround is enabled |
status | ComputedRef<'pending' | 'success' | 'not-found' | 'error'> | Explicit route state |
error | ComputedRef<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.
Check status, not only page, when route behavior matters. page is also
null while a navigation is pending and after an error; the status keeps those
states distinct from a settled missing document.
useContentLocalePath()
Create language-switch links from an existing page result. The function makes no content request and stores no global page state.
const contentPage = await useContentPage(docs, { fallback: true })
const switchLocalePath = useContentLocalePath(contentPage)
const englishPath = computed(() => switchLocalePath('en'))The result is a path string or undefined. A missing alternate, pending page,
missing document, failed query, or stale document
returns undefined. Disable or omit that language link. Ginko uses proven
alternates and prefers a real translation over a supplied fallback alternate.
It does not invent a translated slug.
Interactive links preserve the current query and hash. Ginko does not translate heading anchors. Build canonical and hreflang entries from document route facts and your indexing policy, not from this interactive link function.
Put language links in a shared header
Load the page before rendering its layout. Passing the result to the layout makes the header correct during server rendering. A child page cannot update HTML that an outer header has already rendered.
<script setup lang="ts">
import { useContentPage } from '@lupinum/ginko-content/client'
import { docs } from '~~/content.config'
definePageMeta({ layout: false, key: route => route.path })
const contentPage = await useContentPage(docs, { fallback: true })
const { page, status, error } = contentPage
if (status.value === 'error') throw error.value
if (status.value === 'not-found') {
throw createError({ statusCode: 404, statusMessage: 'Document not found', fatal: true })
}
</script>
<template>
<NuxtLayout name="default" :content-page="contentPage">
<ContentRenderer v-if="page" :value="page" />
</NuxtLayout>
</template><script setup lang="ts">
import { useContentLocalePath, type ContentLocalePage } from '@lupinum/ginko-content/client'
const props = defineProps<{ contentPage?: ContentLocalePage }>()
const switchLocalePath = useContentLocalePath(() => props.contentPage, {
fallback: useSwitchLocalePath()
})
const englishPath = computed(() => switchLocalePath('en'))
</script>
<template>
<header>
<NuxtLink v-if="englishPath" :to="englishPath">English</NuxtLink>
<span v-else aria-disabled="true">English</span>
</header>
<main><slot /></main>
</template>An application-only page can use the same layout normally. With no content page
result, Ginko calls the optional fallback function only when the requested
locale is in the global content locale configuration (runtime.locales).
Otherwise, it returns undefined without calling the function. Ginko returns
the callback's value unchanged. Content pages use their document's alternates,
including collection-specific locales. A content page with no translation never
calls application fallback. Callback errors propagate.
The source accepts a result, ref, or getter. Read the returned link function in
a computed value or template so it updates. ContentLocalePage selects page
and status from the existing page result; it introduces no new document shape.
This pattern remounts the page-owned layout on path changes. Keep persistent application controls outside it if needed. A persistent outer header must receive a result loaded by an ancestor before it renders; do not publish child page facts into a global variable as an SSR workaround.
useContentSearch()
Create headless search state for a combobox, modal, or command palette.
useContentSearch(options?): Promise<UseContentSearchResult>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:
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
| Option | Type | Default | Purpose |
|---|---|---|---|
initialQuery | MaybeRefOrGetter<string> | '' | Initial writable query value |
limit | MaybeRefOrGetter<number> | engine result count | Limit exposed results |
locale | MaybeRefOrGetter<string> | none | Filter results and collection data |
collection | Handle or dynamic name | none | Also load search sections and search navigation |
Return value
| Field | Purpose |
|---|---|
query | Writable search term ref |
results | Normalized, limited search results |
pending / error | Backend loading state |
activeIndex / activeResult | Current highlighted result |
hasQuery / hasResults / isEmpty | Derived rendering state |
setQuery(value) / reset() | Query controls |
next() / previous() / setActiveIndex(index) | Highlight controls |
select(index?) | Return a result or null without navigating |
files | Collection search sections; empty without collection |
searchNavigation | Search-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:
| Export | Signature | Purpose |
|---|---|---|
getCollectionPath | (collection, options?) => string | Project a collection route from locale, slug, or path; localePrefix: false omits the application locale prefix |
findFirstNavigationPage | (items?) => item | null | Find 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?) => Toc | Derive 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. Link ids match the ids that the Markdown
parser gives plain-text headings, including parent prefixes such as
intro-details and suffixes such as intro-1 for repeated headings.
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.