Implement Algolia search UIs in Nuxt 3 apps using @atoms-studio/nuxt-swiftsearch — an SSR-first, fully typed alternative to vue-instantsearch.
Use this skill when building search UIs in Nuxt 3 apps with Algolia via @atoms-studio/nuxt-swiftsearch.
npx nuxi@latest module add swiftsearch
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@atoms-studio/nuxt-swiftsearch"],
});
Every widget requires a composable (in ) AND a component (in ). The composable registers the widget with the InstantSearch instance; the component renders it.
<script setup><template><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>
widgets must be computed — not a plain array. The reactivity is required for SSR hydration.configuration must be ref — wrap the config object in ref().AisRefinementList, AisMenu, etc., the attribute prop on the component must match the attribute in the composable.useAis* composables or Ais* components.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 |
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
To sync search state with the URL:
// app/router.options.ts
import type { RouterConfig } from "@nuxt/schema";
import qs from "qs";
export default <RouterConfig>{
parseQuery: qs.parse,
stringifyQuery: qs.stringify,
};
<script setup lang="ts">
const algoliaRouter = useAisRouter();
const configuration = ref({
indexName: "my_index",
routing: algoliaRouter.value,
searchClient: client,
});
</script>
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>
For stateful caching across page navigations:
const cache = useAisStatefulCache();
const infiniteCache = useAisInfiniteHitsStatefulCache();
const widgets = computed(() => [useAisInfiniteHits({ cache: infiniteCache })]);
<script setup lang="ts">
const { getInstance } = useAisInstantSearch();
// Access the raw InstantSearch instance for advanced use cases
</script>
<AisInstantSearch :widgets :configuration>
<AisIndex index="products">
<AisHits />
</AisIndex>
<AisIndex index="articles">
<AisHits />
</AisIndex>
</AisInstantSearch>
Components use BEM convention: ais-WidgetName-element--modifier. Use useSuit("WidgetName") to generate class names programmatically.
widgets array (component renders but doesn't work)computed() for widgetsattribute between component prop and composable paramalgoliasearch v5+qs (only if using routing)