Implement Algolia search UIs in Nuxt 3 apps using @atoms-studio/nuxt-swiftsearch — an SSR-first, fully typed alternative to vue-instantsearch.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add atoms-studio/nuxt-swiftsearch --skill skills --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of nuxt-swiftsearch?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/atoms-studio-nuxt-swiftsearch)More formats (shields.io, HTML) on the badges page.
---
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
```bash
npx nuxi@latest module add swiftsearch
```
```ts
// 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.
```vue
<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
1. **`widgets` must be `computed`** — not a plain array. The reactivity is required for SSR hydration.
2. **`configuration` must be `ref`** — wrap the config object in `ref()`.
3. **Attribute props must match** — when using `AisRefinementList`, `AisMenu`, etc., the `attribute` prop on the component must match the `attribute` in the composable.
4. **All composables and components are auto-imported** — no manual imports needed for `useAis*` composables or `Ais*` components.
### Available Components & Composables
> Full props, slots, composable params, and examples for every widget: **[widgets-reference.md](./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:
1. Set up query string parsing:
```ts
// app/router.options.ts
import type { RouterConfig } from "@nuxt/schema";
import qs from "qs";
export default <RouterConfig>{
parseQuery: qs.parse,
stringifyQuery: qs.stringify,
};
```
2. Add the router to configuration:
```vue
<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:
```vue
<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:
```ts
const cache = useAisStatefulCache();
const infiniteCache = useAisInfiniteHitsStatefulCache();
const widgets = computed(() => [useAisInfiniteHits({ cache: infiniteCache })]);
```
### Accessing the Instance
```vue
<script setup lang="ts">
const { getInstance } = useAisInstantSearch();
// Access the raw InstantSearch instance for advanced use cases
</script>
```
### Multi-Index Search
```vue
<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 `widgets` array (component renders but doesn't work)
- Using a plain array instead of `computed()` for widgets
- Mismatched `attribute` between component prop and composable param
- Importing composables manually (they're auto-imported)
## Dependencies
- Nuxt >= 3.10
- `algoliasearch` v5+
- `qs` (only if using routing)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!