Skip to content
Back to skills

nuxt-swiftsearch

ASecurity

Implement Algolia search UIs in Nuxt 3 apps using @atoms-studio/nuxt-swiftsearch — an SSR-first, fully typed alternative to vue-instantsearch.

  • 66 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 12, 2026
developmentgoswiftbashreactvueapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add atoms-studio/nuxt-swiftsearch --skill skills --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of nuxt-swiftsearch?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for nuxt-swiftsearch
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/atoms-studio-nuxt-swiftsearch/badge)](https://www.skillsdirectory.com/skills/atoms-studio-nuxt-swiftsearch)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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)

Files in this skill

  • README.md554 B
  • SKILL.md8 KB
  • widgets-reference.md19.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.