Custom Search
Rspress provides built-in full-text search functionality, but also allows you to customize the search experience or integrate third-party search services. This chapter covers search configuration, customization, and integration with external search providers.
Built-in Search
Enable Built-in Search
Rspress includes FlexSearch-based search by default:
// rspress.config.ts
import { defineConfig } from 'rspress/config';
export default defineConfig({
search: {
// Enable built-in search (enabled by default)
versioned: true,
},
});Search Features
The built-in search provides:
- Full-text search - Search across all documentation content
- Instant results - Real-time search as you type
- Keyboard navigation - Navigate results with arrow keys
- Highlighting - Matched terms are highlighted in results
- Version filtering - Filter by documentation version
Search Keyboard Shortcuts
- Ctrl/Cmd + K - Open search
- Arrow keys - Navigate results
- Enter - Go to selected result
- Esc - Close search
Configure Built-in Search
Basic Configuration
// rspress.config.ts
export default defineConfig({
search: {
// Include/exclude specific paths
include: ['docs/**/*.md'],
exclude: ['**/private/**', '**/temp/**'],
// Search placeholder text
placeholder: 'Search documentation...',
// Maximum number of results
limit: 10,
// Enable versioned search
versioned: true,
// Custom hotkey
hotkey: {
key: 'k',
ctrlKey: true,
},
},
});Advanced Configuration
export default defineConfig({
search: {
// Customize search options
searchOptions: {
// Minimum characters to trigger search
minChars: 2,
// Maximum results per page
maxResults: 50,
// Boost for title matches
titleBoost: 2,
// Boost for heading matches
headingBoost: 1.5,
// Enable fuzzy matching
fuzzy: true,
// Distance for fuzzy matching
distance: 100,
},
// Customize indexing
indexOptions: {
// Fields to index
fields: ['title', 'content', 'headers'],
// Fields to store (returned in results)
store: ['title', 'content', 'url'],
// Tokenizer settings
tokenize: 'forward',
},
},
});Custom Search UI
Override Search Component
Create a custom search component:
// theme/components/CustomSearch.tsx
import { useState, useEffect } from 'react';
import { useSearchIndex } from 'rspress/runtime';
import './CustomSearch.css';
export function CustomSearch() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const searchIndex = useSearchIndex();
const handleSearch = async (searchQuery: string) => {
if (searchQuery.length < 2) {
setResults([]);
return;
}
const searchResults = await searchIndex.search(searchQuery, {
limit: 10,
enrich: true,
});
setResults(searchResults);
};
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
setIsOpen(true);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<>
<button
className="search-button"
onClick={() => setIsOpen(true)}
>
<span className="search-icon">🔍</span>
<span className="search-text">Search...</span>
<kbd className="search-hotkey">⌘K</kbd>
</button>
{isOpen && (
<div className="search-modal">
<div className="search-backdrop" onClick={() => setIsOpen(false)} />
<div className="search-content">
<input
type="text"
className="search-input"
placeholder="Search documentation..."
value={query}
onChange={(e) => {
setQuery(e.target.value);
handleSearch(e.target.value);
}}
autoFocus
/>
<div className="search-results">
{results.map((result) => (
<a
key={result.id}
href={result.url}
className="search-result-item"
onClick={() => setIsOpen(false)}
>
<div className="result-title">{result.title}</div>
<div className="result-excerpt">{result.excerpt}</div>
</a>
))}
{query.length >= 2 && results.length === 0 && (
<div className="no-results">No results found</div>
)}
</div>
</div>
</div>
)}
</>
);
}Custom Search Styles
/* theme/components/CustomSearch.css */
.search-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--rp-c-bg-soft);
border: 1px solid var(--rp-c-border);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.search-button:hover {
background: var(--rp-c-bg-mute);
border-color: var(--rp-c-brand);
}
.search-hotkey {
padding: 0.125rem 0.375rem;
background: var(--rp-c-bg);
border: 1px solid var(--rp-c-divider);
border-radius: 4px;
font-size: 0.75rem;
font-family: var(--rp-font-family-mono);
}
.search-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 10vh;
}
.search-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
.search-content {
position: relative;
width: 90%;
max-width: 600px;
background: var(--rp-c-bg);
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.search-input {
width: 100%;
padding: 1rem 1.5rem;
border: none;
border-bottom: 1px solid var(--rp-c-divider);
font-size: 1rem;
background: transparent;
color: var(--rp-c-text-1);
outline: none;
}
.search-results {
max-height: 400px;
overflow-y: auto;
}
.search-result-item {
display: block;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--rp-c-divider);
text-decoration: none;
color: inherit;
transition: background 0.2s;
}
.search-result-item:hover {
background: var(--rp-c-bg-soft);
}
.result-title {
font-weight: 600;
color: var(--rp-c-brand);
margin-bottom: 0.25rem;
}
.result-excerpt {
font-size: 0.875rem;
color: var(--rp-c-text-2);
line-height: 1.5;
}
.no-results {
padding: 2rem 1.5rem;
text-align: center;
color: var(--rp-c-text-3);
}Algolia DocSearch Integration
Why Algolia DocSearch?
Algolia DocSearch provides:
- Extremely fast search
- Better ranking algorithm
- Typo tolerance
- Analytics and insights
- Free for open-source projects
Setup Algolia DocSearch
- Apply for DocSearch
Visit docsearch.algolia.com and apply for free access.
- Get Your Credentials
After approval, you'll receive:
- Application ID
- API Key
- Index Name
- Configure in Rspress
// rspress.config.ts
export default defineConfig({
search: {
algolia: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_SEARCH_API_KEY',
indexName: 'YOUR_INDEX_NAME',
// Optional configuration
searchParameters: {
facetFilters: ['language:en', 'version:1.0'],
},
// Custom placeholder
placeholder: 'Search docs...',
// Custom translations
translations: {
button: {
buttonText: 'Search',
buttonAriaLabel: 'Search documentation',
},
modal: {
searchBox: {
resetButtonTitle: 'Clear',
resetButtonAriaLabel: 'Clear',
cancelButtonText: 'Cancel',
cancelButtonAriaLabel: 'Cancel',
},
footer: {
selectText: 'to select',
navigateText: 'to navigate',
closeText: 'to close',
},
},
},
},
},
});Customize Algolia UI
/* Custom Algolia search styles */
.DocSearch-Button {
background: var(--rp-c-bg-soft);
border: 1px solid var(--rp-c-border);
border-radius: 8px;
}
.DocSearch-Button:hover {
background: var(--rp-c-bg-mute);
border-color: var(--rp-c-brand);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.DocSearch-Modal {
--docsearch-primary-color: var(--rp-c-brand);
--docsearch-text-color: var(--rp-c-text-1);
--docsearch-modal-background: var(--rp-c-bg);
--docsearch-searchbox-background: var(--rp-c-bg-soft);
}Custom Search Provider
Integrate Third-Party Search
Create a custom search provider:
// lib/customSearch.ts
export interface SearchProvider {
search(query: string): Promise<SearchResult[]>;
index(content: any[]): Promise<void>;
}
export interface SearchResult {
id: string;
title: string;
excerpt: string;
url: string;
score: number;
}
export class CustomSearchProvider implements SearchProvider {
private apiEndpoint: string;
constructor(config: { apiEndpoint: string }) {
this.apiEndpoint = config.apiEndpoint;
}
async search(query: string): Promise<SearchResult[]> {
const response = await fetch(`${this.apiEndpoint}/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
});
const data = await response.json();
return data.results;
}
async index(content: any[]): Promise<void> {
await fetch(`${this.apiEndpoint}/index`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content }),
});
}
}Use Custom Provider
// rspress.config.ts
import { CustomSearchProvider } from './lib/customSearch';
export default defineConfig({
search: {
provider: new CustomSearchProvider({
apiEndpoint: 'https://api.example.com',
}),
},
});Search Analytics
Track Search Queries
// theme/components/SearchAnalytics.tsx
import { useEffect } from 'react';
export function useSearchAnalytics() {
useEffect(() => {
const handleSearch = (e: CustomEvent) => {
const { query, results } = e.detail;
// Send to analytics
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'search', {
search_term: query,
results_count: results.length,
});
}
};
window.addEventListener('rspress:search', handleSearch as EventListener);
return () => {
window.removeEventListener('rspress:search', handleSearch as EventListener);
};
}, []);
}Monitor Popular Searches
// lib/searchAnalytics.ts
export class SearchAnalytics {
private searches: Map<string, number> = new Map();
trackSearch(query: string, resultsCount: number) {
const count = this.searches.get(query) || 0;
this.searches.set(query, count + 1);
// Log searches with no results
if (resultsCount === 0) {
console.log(`No results for: ${query}`);
}
}
getTopSearches(limit: number = 10): [string, number][] {
return Array.from(this.searches.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, limit);
}
}Search Optimization
Improve Search Indexing
// rspress.config.ts
export default defineConfig({
search: {
indexOptions: {
// Index custom fields
fields: {
title: { weight: 3 }, // Higher weight for titles
headings: { weight: 2 }, // Medium weight for headings
content: { weight: 1 }, // Lower weight for content
tags: { weight: 2 }, // Weight for tags
},
// Exclude noise words
stopWords: ['the', 'a', 'an', 'and', 'or', 'but'],
// Stemming for better matching
stemming: true,
// Case sensitivity
caseSensitive: false,
},
},
});Add Search Metadata
Improve search results with frontmatter:
---
title: Custom Search
description: Learn how to customize search in Rspress
tags: [search, customization, algolia]
keywords: search, custom search, algolia, docsearch
---
# Custom Search
Your content here...Troubleshooting
Search Not Working
Check configuration:
// Ensure search is enabled
export default defineConfig({
search: true, // or detailed config object
});Poor Search Results
Improve indexing:
export default defineConfig({
search: {
// Include more content
include: ['**/*.md', '**/*.mdx'],
// Adjust weights
indexOptions: {
fields: {
title: { weight: 5 },
content: { weight: 1 },
},
},
},
});Algolia Not Loading
Check credentials and network:
// Verify credentials
console.log('App ID:', process.env.ALGOLIA_APP_ID);
console.log('API Key:', process.env.ALGOLIA_API_KEY);
// Check if Algolia is loaded
if (typeof window !== 'undefined' && window.docsearch) {
console.log('Algolia loaded successfully');
}Best Practices
1. Optimize Index Size
Keep search index manageable:
export default defineConfig({
search: {
// Exclude unnecessary content
exclude: [
'**/generated/**',
'**/temp/**',
'**/*.test.md',
],
},
});2. Use Descriptive Titles
Help users find content:
---
title: How to Configure Custom Search
---
Better than: "Configuration"3. Add Search Keywords
Include common search terms:
---
keywords: [search, find, lookup, query]
---4. Test Search Regularly
Monitor search quality:
# Test common search queries
- "getting started"
- "configuration"
- "api reference"Next Steps
- Learn about Built-in Components to enhance content
- Explore Build Extensions for advanced features
- Check out Custom Theme for styling search UI
💡 Search Tips
- Use Algolia DocSearch for public documentation (it's free!)
- Implement search analytics to understand user needs
- Regularly test and optimize search results
- Add synonyms for common terms
📊 Search Performance
- Built-in search: Best for small to medium sites (<500 pages)
- Algolia: Best for large sites (500+ pages) or public docs
- Custom provider: Best for specific requirements or existing infrastructure
⚠️ Privacy Considerations
- Search queries may be logged for analytics
- Consider privacy policies when using third-party search
- Allow users to opt-out of search tracking if needed