Imported from atoms-studio/nuxt-swiftsearch (
skills/SKILL.md). Install upstream withnpx skills add atoms-studio/nuxt-swiftsearch --skill skills. Copyright stays with the author (MIT).
Instructions
Use this skill when building search UIs in Nuxt 3 apps with Algolia via @atoms-studio/nuxt-swiftsearch.
When to Use
- Adding search functionality to a Nuxt 3 app with Algolia
- Building search pages with filters, pagination, infinite scroll
- Implementing SSR-compatible search experiences
- Migrating from vue-instantsearch to a Nuxt-native approach
Installation
npx nuxi@latest module add swiftsearch
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@atoms-studio/nuxt-swiftsearch"],
});
Core Pattern: Dual Registration
Every widget requires both a composable (in <script setup>) AND a component (in <template>). The composable registers the widget with the InstantSearch instance; the component renders it.
<template>
<AisInstantSearch :widgets :configuration>
<AisSearchBox />
<AisHits />
<AisRefinementList attribute="brand" />
</AisInstantSearch>
</template>
<script setup lang="ts">
import { algoliasearch } from "algoliasearch";
const client = algoliasearch("APP_ID", "API_KEY");
const widgets = computed(() => [
useAisSearchBox({}),
useAisHits({ escapeHTML: true }),
useAisRefinementList({ attribute: "brand" }),
]);
const configuration = ref({
indexName: "my_index",
searchClient: client,
});
</script>
Critical Rules
widgetsmust becomputed— not a plain array. The reactivity is required for SSR hydration.configurationmust beref— wrap the config object inref().- Attribute props must match — when using
AisRefinementList,AisMenu, etc., theattributeprop on the component must match theattributein the composable. - All composables and components are auto-imported — no manual imports needed for
useAis*composables orAis*components.
Available Components & Composables
Full props, slots, composable params, and examples for every widget: widgets-reference.md
| Component | Composable | Purpose |
|---|---|---|
<AisInstantSearch> |
— | Root wrapper, receives :widgets and :configuration |
<AisIndex> |
useAisIndex() |
Multi-index scoping |
<AisSearchBox> |
useAisSearchBox() |
Search input |
<AisHits> |
useAisHits() |
Paginated results |
<AisInfiniteHits> |
useAisInfiniteHits() |
Infinite scroll results |
<AisRefinementList> |
useAisRefinementList() |
Facet filtering |
<AisMenu> |
useAisMenu() |
Single-select facet menu |
<AisMenuSelect> |
— | Dropdown facet menu |
<AisHierarchicalMenu> |
useAisHierarchicalMenu() |
Nested category navigation |
<AisNumericMenu> |
useAisNumericMenu() |
Numeric range filtering |
<AisRangeInput> |
useAisRangeInput() |
Min/max range input |
<AisRatingMenu> |
useAisRatingMenu() |
Star rating filter |
<AisToggleRefinement> |
useAisToggleRefinement() |
Boolean toggle filter |
<AisClearRefinements> |
useAisClearRefinements() |
Clear all active filters |
<AisCurrentRefinements> |
useAisCurrentRefinements() |
Show active filters |
<AisSortBy> |
useAisSortBy() |
Sort order selector |
<AisStats> |
useAisStats() |
Results count & timing |
<AisPagination> |
useAisPagination() |
Page navigation |
<AisConfigure> |
useAisConfigure() |
Hidden search parameters |
<AisHighlight> |
— | Highlight matched text |
<AisAutocomplete> |
useAisAutocomplete() |
Autocomplete suggestions |
<AisPanel> |
— | Collapsible panel wrapper |
<AisQueryRuleCustomData> |
useAisQueryRuleCustomData() |
Query rules data |
<AisStateResults> |
— | Generic state renderer |
Widget Categories (Quick Lookup)
Search Input: AisSearchBox, AisAutocomplete
Results: AisHits, AisInfiniteHits, AisStateResults
Refinements: AisRefinementList, AisMenu, AisMenuSelect, AisHierarchicalMenu, AisNumericMenu, AisRangeInput, AisRatingMenu, AisToggleRefinement
Active Filters: AisClearRefinements, AisCurrentRefinements
Sorting & Pagination: AisSortBy, AisPagination
Display: AisStats, AisHighlight, AisPanel
Config: AisConfigure, AisQueryRuleCustomData
Structure: AisInstantSearch, AisIndex
URL Routing
To sync search state with the URL:
- Set up query string parsing:
// app/router.options.ts
import type { RouterConfig } from "@nuxt/schema";
import qs from "qs";
export default <RouterConfig>{
parseQuery: qs.parse,
stringifyQuery: qs.stringify,
};
- Add the router to configuration:
<script setup lang="ts">
const algoliaRouter = useAisRouter();
const configuration = ref({
indexName: "my_index",
routing: algoliaRouter.value,
searchClient: client,
});
</script>
Slot Customization
All components expose slots with typed render state:
<AisHits>
<template #item="{ item }">
<div>{{ item.name }}</div>
</template>
</AisHits>
<AisInfiniteHits>
<template #item="{ item }">
<div>{{ item.title }}</div>
</template>
<template #loadMore="{ refineNext, isLastPage }">
<button :disabled="isLastPage" @click="refineNext">Load more</button>
</template>
</AisInfiniteHits>
Caching (Infinite Hits)
For stateful caching across page navigations:
const cache = useAisStatefulCache();
const infiniteCache = useAisInfiniteHitsStatefulCache();
const widgets = computed(() => [useAisInfiniteHits({ cache: infiniteCache })]);
Accessing the Instance
<script setup lang="ts">
const { getInstance } = useAisInstantSearch();
// Access the raw InstantSearch instance for advanced use cases
</script>
Multi-Index Search
<AisInstantSearch :widgets :configuration>
<AisIndex index="products">
<AisHits />
</AisIndex>
<AisIndex index="articles">
<AisHits />
</AisIndex>
</AisInstantSearch>
CSS Classes
Components use BEM convention: ais-WidgetName-element--modifier. Use useSuit("WidgetName") to generate class names programmatically.
Common Mistakes
- Forgetting to add the composable to
widgetsarray (component renders but doesn't work) - Using a plain array instead of
computed()for widgets - Mismatched
attributebetween component prop and composable param - Importing composables manually (they're auto-imported)
Dependencies
- Nuxt >= 3.10
algoliasearchv5+qs(only if using routing)