name: nuxt-swiftsearch description: Implement Algolia search UIs in Nuxt 3 apps using @atoms-studio/nuxt-swiftsearch — an SSR-first, fully typed alternative to vue-instantsearch. version: 1.0.0 license: MIT author: "@atoms-studio" tags:
- nuxt
- algolia
- search
- ssr
- vue
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)