Add search and navigation
Add MiniSearch and an ordered navigation menu to a routed content site.
Turn on MiniSearch in nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@lupinum/ginko-content'],
content: {
search: {
engine: 'minisearch'
}
}
})For the filesystem provider, Ginko builds the index from page content and ships it with the site. You do not need to run a search service.
Add a search box
useContentSearch returns headless state: the query ref, loading and error flags, and ranked results with a path and heading anchor. Wire it to your own markup:
<script setup lang="ts">
import { useContentSearch } from '@lupinum/ginko-content/client'
import { pages } from '~~/content.config'
const search = await useContentSearch({
collection: pages,
limit: 8
})
</script>
<template>
<section aria-label="Search content">
<label for="content-search">Search</label>
<input
id="content-search"
:value="search.query.value"
type="search"
@input="search.setQuery(($event.target as HTMLInputElement).value)"
>
<p v-if="search.pending.value">Searching…</p>
<p v-else-if="search.error.value">Search is unavailable.</p>
<ul v-else-if="search.hasResults.value">
<li
v-for="result in search.results.value"
:key="`${result.path}:${result.anchor || ''}`"
>
<NuxtLink :to="result.anchor ? `${result.path}#${result.anchor}` : result.path">
<strong>{{ result.title }}</strong>
<span>{{ result.excerpt }}</span>
</NuxtLink>
</li>
</ul>
</section>
</template>Passing collection: pages also loads that collection's search sections and navigation tree, which prebuilt UIs like Nuxt UI's UContentSearch need. Omit collection when you only want ranked query results and no extra request.
Add a navigation menu
navigation() returns an ordered tree from the same collection. Pair it with useAsyncData and render it:
<script setup lang="ts">
import { navigation } from '@lupinum/ginko-content/client'
import { pages } from '~~/content.config'
const { data: items } = await useAsyncData('pages-navigation', () =>
navigation(pages, {
select: ['description']
})
)
</script>
<template>
<nav aria-label="Content">
<ul>
<li v-for="(item, index) in items" :key="item.path ?? `group:${index}`">
<NuxtLink v-if="item.path" :to="item.path">
{{ item.title }}
</NuxtLink>
<span v-else>{{ item.title }}</span>
<ul v-if="item.children?.length">
<li v-for="(child, childIndex) in item.children" :key="child.path ?? `group:${index}:${childIndex}`">
<NuxtLink v-if="child.path" :to="child.path">
{{ child.title }}
</NuxtLink>
<span v-else>{{ child.title }}</span>
</li>
</ul>
</li>
</ul>
</nav>
</template>A folder set up as a structural group in .navigation.yml has children but no path. That is the v-else branch: render its title as plain text, then keep walking its children.
For this filesystem-backed site, the production build writes the MiniSearch index and uses the generated content snapshot for navigation. Verify both in a production preview:
pnpm build
pnpm previewSearch for a heading and follow several nested navigation links before deploying.