> For AI agents: the complete documentation index is available at https://rspress.rs/llms.txt, the full documentation bundle is available at https://rspress.rs/llms-full.txt.

# @rspress/plugin-algolia 

[Source Code](https://github.com/web-infra-dev/rspress/tree/main/packages/plugin-algolia)

This plugin replaces Rspress's built-in search with [Algolia](https://www.algolia.com/) through [DocSearch](https://docsearch.algolia.com).

## Installation


```sh [npm]
npm add @rspress/plugin-algolia -D
```

```sh [yarn]
yarn add @rspress/plugin-algolia -D
```

```sh [pnpm]
pnpm add @rspress/plugin-algolia -D
```

```sh [bun]
bun add @rspress/plugin-algolia -D
```

```sh [deno]
deno add npm:@rspress/plugin-algolia -D
```

## Usage

First, add the following configuration to `rspress.config.ts`:

```ts
// rspress.config.ts
import path from 'path';
import { defineConfig } from '@rspress/core';
import { pluginAlgolia } from '@rspress/plugin-algolia';

export default defineConfig({
  plugins: [pluginAlgolia()],
});
```

Then override the `Search` component with an Algolia-powered search box through [Custom Theme](https://rspress.rs/guide/basic/custom-theme.md).

```tsx
// theme/index.tsx
import { Search as PluginAlgoliaSearch } from '@rspress/plugin-algolia/runtime';

const Search = () => {
  return (
    <PluginAlgoliaSearch
      docSearchProps={{
        appId: 'R2IYF7ETH7', // Replace with your own Algolia appId
        apiKey: '599cec31baffa4868cae4e79f180729b', // Replace with your own Algolia apiKey
        indexName: 'docsearch', // Replace with your own Algolia indexName
      }}
    />
  );
};
export { Search };
export * from '@rspress/core/theme-original';
```

## Configuration

The plugin accepts an options object with the following type:

```ts
interface Options {
  verificationContent?: string;
}
```

### verificationContent

- **Type**: `string | undefined`
- **Default**: `undefined`

Used for meta tag verification when creating an Algolia crawler. Format: `<meta name="algolia-site-verification" content="YOUR_VERIFICATION_CONTENT" />`. See [Create a new crawler - Algolia](https://www.algolia.com/doc/tools/crawler/getting-started/create-crawler/#dns).

## SearchProps

The `SearchProps` type from `@rspress/plugin-algolia/runtime` is as follows:

```ts
import type { DocSearchProps } from '@docsearch/react';

type Locales = Record<
  string,
  { translations: DocSearchProps['translations']; placeholder: string }
>;
type SearchProps = {
  /**
   * @link https://docsearch.algolia.com/docs/api
   */
  docSearchProps?: DocSearchProps;
  locales?: Locales;
};
```

### docSearchProps

- **Type**: `import('@docsearch/react').DocSearchProps`
- **Default**: `undefined`

`docSearchProps` is passed directly to the `<DocSearch />` component from `@docsearch/react`. For specific types, see the [DocSearch documentation](https://docsearch.algolia.com/docs/api).

### locales

- **Type**:

```ts
type Locales = Record<
  string,
  { translations: DocSearchProps['translations']; placeholder: string }
>;
```

- **Default**: `{}`

For customizing translated text in different languages, Rspress provides the following translated text for import.


```ts file="<root>/../packages/plugin-algolia/src/runtime/locales.ts"
import type { DocSearchProps } from '@docsearch/react';

export type Locales = Record<
  string,
  { translations: DocSearchProps['translations']; placeholder: string }
>;

// cspell:disable
export const ZH_LOCALES: Locales = {
  zh: {
    placeholder: '搜索文档',
    translations: {
      button: {
        buttonText: '搜索',
        buttonAriaLabel: '搜索',
      },
      modal: {
        searchBox: {
          clearButtonTitle: '清除查询条件',
          clearButtonAriaLabel: '清除查询条件',
          closeButtonText: '取消',
          closeButtonAriaLabel: '取消',
        },
        startScreen: {
          recentSearchesTitle: '搜索历史',
          noRecentSearchesText: '没有搜索历史',
          saveRecentSearchButtonTitle: '保存至搜索历史',
          removeRecentSearchButtonTitle: '从搜索历史中移除',
          favoriteSearchesTitle: '收藏',
          removeFavoriteSearchButtonTitle: '从收藏中移除',
        },
        errorScreen: {
          titleText: '无法获取结果',
          helpText: '你可能需要检查你的网络连接',
        },
        footer: {
          selectText: '选择',
          navigateText: '切换',
          closeText: '关闭',
          poweredByText: '搜索提供者',
        },
        noResultsScreen: {
          noResultsText: '无法找到相关结果',
          suggestedQueryText: '你可以尝试查询',
          reportMissingResultsText: '你认为该查询应该有结果？',
          reportMissingResultsLinkText: '点击反馈',
        },
      },
    },
  },
} as const;

export const RU_LOCALES: Locales = {
  ru: {
    placeholder: 'Поиск в документации',
    translations: {
      button: {
        buttonText: 'Поиск',
        buttonAriaLabel: 'Поиск',
      },
      modal: {
        searchBox: {
          clearButtonTitle: 'Очистить поиск',
          clearButtonAriaLabel: 'Очистить поиск',
          closeButtonText: 'Закрыть',
          closeButtonAriaLabel: 'Закрыть',
        },
        startScreen: {
          recentSearchesTitle: 'История поиска',
          noRecentSearchesText: 'Нет истории поиска',
          saveRecentSearchButtonTitle: 'Сохранить в истории поиска',
          removeRecentSearchButtonTitle: 'Удалить из истории поиска',
          favoriteSearchesTitle: 'Избранное',
          removeFavoriteSearchButtonTitle: 'Удалить из избранного',
        },
        errorScreen: {
          titleText: 'Невозможно получить результаты',
          helpText: 'Проверьте подключение к Интернету',
        },
        footer: {
          selectText: 'выбрать',
          navigateText: 'перейти',
          closeText: 'закрыть',
          poweredByText: 'поиск от',
        },
        noResultsScreen: {
          noResultsText: 'Ничего не найдено',
          suggestedQueryText: 'Попробуйте изменить запрос',
          reportMissingResultsText: 'Считаете, что результаты должны быть?',
          reportMissingResultsLinkText: 'Сообщите об этом',
        },
      },
    },
  },
} as const;

export const TA_LOCALES: Locales = {
  ta: {
    placeholder: 'ஆவணங்களைத் தேடுக...',
    translations: {
      button: {
        buttonText: 'தேடுக',
        buttonAriaLabel: 'தேடுக',
      },
      modal: {
        searchBox: {
          clearButtonTitle: 'தேடலை நீக்கவும்',
          clearButtonAriaLabel: 'தேடலை நீக்கவும்',
          closeButtonText: 'இரத்து செய்',
          closeButtonAriaLabel: 'இரத்து செய்',
        },
        startScreen: {
          recentSearchesTitle: 'சமீபத்திய தேடல்கள்',
          noRecentSearchesText: 'சமீபத்திய தேடல்கள் எதுவும் இல்லை',
          saveRecentSearchButtonTitle: 'தேடல் வரலாற்றில் சேர்க்கவும்',
          removeRecentSearchButtonTitle: 'தேடல் வரலாற்றிலிருந்து நீக்கவும்',
          favoriteSearchesTitle: 'பிடித்தவை',
          removeFavoriteSearchButtonTitle: 'பிடித்தவையிலிருந்து நீக்கவும்',
        },
        errorScreen: {
          titleText: 'முடிவுகளைப் பெற முடியவில்லை',
          helpText: 'உங்கள் இணைய இணைப்பைச் சரிபார்க்கவும்',
        },
        footer: {
          selectText: 'தேர்ந்தெடுக்க',
          navigateText: 'நகர்த்த',
          closeText: 'மூட',
          poweredByText: 'வழங்குபவர்',
        },
        noResultsScreen: {
          noResultsText: 'முடிவுகள் கிடைக்கவில்லை',
          suggestedQueryText: 'இவற்றைத் தேட முயற்சிக்கவும்',
          reportMissingResultsText:
            'இதற்கு முடிவுகள் கிடைத்திருக்க வேண்டும் என நினைக்கிறீர்களா?',
          reportMissingResultsLinkText: 'எங்களுக்குத் தெரியப்படுத்துங்கள்',
        },
      },
    },
  },
} as const;
// cspell:enable

```

Rspress provides Chinese translations by default, and you can customize translated text for different languages through `locales`.

- Example:

```tsx
import { Search as PluginAlgoliaSearch, ZH_LOCALES } from '@rspress/plugin-algolia/runtime';

<PluginAlgoliaSearch locales={ZH_LOCALES} />
// or
<PluginAlgoliaSearch
  locales={{
    en: {
      placeholder: 'Search Documentation',
      translations: {
        button: {
          buttonText: 'Search',
          buttonAriaLabel: 'Search',
        }
      }
    },
    ...ZH_LOCALES,
  }}
/>
```

## Algolia crawler config

Here is an example config based on what this site uses:


```tsx
new Crawler({
  appId: 'YOUR_APP_ID',
  apiKey: 'YOUR_API_KEY',
  rateLimit: 8,
  maxDepth: 10,
  startUrls: ['https://rspress.rs'],
  sitemaps: ['https://rspress.rs/sitemap.xml'],
  discoveryPatterns: ['https://rspress.rs/**'],
  actions: [
    {
      indexName: 'doc_search_rspress_v2_pages',
      pathsToMatch: ['https://rspress.rs/**'],
      recordExtractor: ({ $, helpers }) => {
        // Remove badge elements to prevent their text from being indexed
        $('.rp-badge').remove();
        // Remove non-doc elements (e.g. version switcher) from h1 to keep title text clean
        $('.rspress-doc h1 .rp-not-doc').remove();

        const $activeNavItem = $('.rp-nav-menu__item.rp-nav-menu__item--active')
          .first()
          .clone();

        const lvl0 = $activeNavItem.text().trim() || 'Documentation';

        return helpers.docsearch({
          recordProps: {
            lvl0: {
              selectors: '',
              defaultValue: lvl0,
            },
            lvl1: '.rspress-doc h1',
            lvl2: '.rspress-doc h2',
            lvl3: '.rspress-doc h3',
            lvl4: '.rspress-doc h4',
            lvl5: '.rspress-doc h5',
            lvl6: '.rspress-doc pre > code', // if you want to search code blocks, add this line
            content: '.rspress-doc p, .rspress-doc li',
          },
          indexHeadings: true,
          aggregateContent: true,
          recordVersion: 'v3',
        });
      },
    },
  ],
  initialIndexSettings: {
    doc_search_rspress_v2_pages: {
      attributesForFaceting: ['type', 'lang'],
      attributesToRetrieve: ['hierarchy', 'content', 'anchor', 'url'],
      attributesToHighlight: ['hierarchy', 'content'],
      attributesToSnippet: ['content:10'],
      camelCaseAttributes: ['hierarchy', 'content'],
      searchableAttributes: [
        'unordered(hierarchy.lvl0)',
        'unordered(hierarchy.lvl1)',
        'unordered(hierarchy.lvl2)',
        'unordered(hierarchy.lvl3)',
        'unordered(hierarchy.lvl4)',
        'unordered(hierarchy.lvl5)',
        'unordered(hierarchy.lvl6)',
        'content',
      ],
      distinct: true,
      attributeForDistinct: 'url',
      customRanking: [
        'desc(weight.pageRank)',
        'desc(weight.level)',
        'asc(weight.position)',
      ],
      ranking: [
        'words',
        'filters',
        'typo',
        'attribute',
        'proximity',
        'exact',
        'custom',
      ],
      minWordSizefor1Typo: 3,
      minWordSizefor2Typos: 7,
      allowTyposOnNumericTokens: false,
      minProximity: 1,
      ignorePlurals: true,
      advancedSyntax: true,
      attributeCriteriaComputedByMinProximity: true,
      removeWordsIfNoResults: 'allOptional',
    },
  },
  schedule: 'on tuesday',
  indexPrefix: 'rspress-v2-crawler-',
});
```

## Distinguish search results based on i18n

You can achieve internationalized search results by combining [Runtime API](https://rspress.rs/ui/hooks/index.md) with `docSearchProps`.

Here's an example using `docSearchProps.searchParameters`:

```tsx
// theme/index.tsx
import { useLang } from '@rspress/core/runtime';
import { Search as PluginAlgoliaSearch } from '@rspress/plugin-algolia/runtime';

const Search = () => {
  const lang = useLang();
  return (
    <PluginAlgoliaSearch
      docSearchProps={{
        appId: 'R2IYF7ETH7',
        apiKey: '599cec31baffa4868cae4e79f180729b',
        indexName: 'docsearch',
        searchParameters: {
          facetFilters: [`lang:${lang}`],
        },
      }}
    />
  );
};
export { Search };
export * from '@rspress/core/theme-original';
```
