Advanced

Full-Text Search

Implement full-text search in your website using Nuxt Content.

Content module provides useSearchCollection, a zero-dependency composable powered by SQLite FTS5. It builds an inverted index from your content sections and provides instant ranked search with prefix matching and snippets.

SearchExample.vue
<script setup lang="ts">
const { status, search } = useSearchCollection('docs')
const query = ref('')
const results = ref([])

watch(query, async (value) => {
  results.value = value
    ? await search(value, { snippet: { columns: ['content'], around: 40 } })
    : []
})
</script>

<template>
  <UInput v-model="query" :disabled="status !== 'ready'" placeholder="Search..." />
  <ul>
    <li v-for="result in results" :key="result.id">
      <NuxtLink :to="result.id">{{ result.title }}</NuxtLink>
      <p v-if="result.snippets?.content" v-html="result.snippets.content" />
    </li>
  </ul>
</template>
Read more about useSearchCollection composable.

External Libraries

You can also use queryCollectionSearchSections to get raw section data and pass it to search libraries like Fuse.js or MiniSearch. This approach gives you typo-tolerant fuzzy matching at the cost of loading all sections into memory.

MiniSearch

MiniSearch is a lightweight full-text search library with prefix matching, fuzzy search, and field boosting.

<script setup lang="ts">
import MiniSearch from 'minisearch'

const query = ref('')
const { data } = await useAsyncData('search', () => queryCollectionSearchSections('docs'))

const miniSearch = new MiniSearch({
  fields: ['title', 'content'],
  storeFields: ['title', 'content'],
  searchOptions: {
    prefix: true,
    fuzzy: 0.2,
  },
})

// Add data to the MiniSearch instance
miniSearch.addAll(toValue(data.value))
const result = computed(() => miniSearch.search(toValue(query)))
</script>

<template>
  <UContainer class="p-4">
    <UCard>
      <UInput v-model="query" placeholder="Search..." />
      <ul>
        <li v-for="link of result" :key="link.id" class="mt-2">
          <NuxtLink :to="link.id">{{ link.title }}</NuxtLink>
          <p class="text-gray-500 text-xs">{{ link.content }}</p>
        </li>
      </ul>
    </UCard>
  </UContainer>
</template>

Fuse.js

Fuse.js is a fuzzy search library that uses the Bitap algorithm for typo-tolerant matching with configurable thresholds.

<script setup lang="ts">
import Fuse from 'fuse.js'

const query = ref('')
const { data } = await useAsyncData('search-data', () => queryCollectionSearchSections('docs'))

const fuse = new Fuse(data.value, {
  keys: ['title', 'description']
})

const result = computed(() => fuse.search(toValue(query)).slice(0, 10))
</script>

<template>
  <UContainer class="p-4">
    <UCard>
      <UInput v-model="query" placeholder="Search..." class="w-full" />
      <ul>
        <li v-for="link of result" :key="link.item.id" class="mt-2">
          <UButton variant="ghost" class="w-full" :to="link.item.id">
            {{ link.item.title }}
            <span class="text-gray-500 text-xs">
              {{ link.item.content?.slice(0, 100) }}...
            </span>
          </UButton>
        </li>
      </ul>
    </UCard>
  </UContainer>
</template>

Nuxt UI

Nuxt UI provides a ContentSearch component that is powered by fuse.js and accepts the output of queryCollectionSearchSections directly.

<script setup lang="ts">
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))
const { data: files } = await useAsyncData('search', () => queryCollectionSearchSections('docs'))

const searchTerm = ref('')
</script>

<template>
  <UContentSearch
    v-model:search-term="searchTerm"
    :files="files"
    :navigation="navigation"
    :fuse="{ resultLimit: 42 }"
  />
</template>
Copyright © 2026