AI-Powered Search for Modern React Applications
Add AI-powered search to any React app in minutes. One HTTP request, a small hook you own, zero-config setup.
Building search in React means choosing between expensive SaaS, complex infrastructure, or months of custom development.
Algolia pricing scales aggressively as your user base grows, and the setup is more complex than most React projects need.
Self-hosted ElasticSearch requires DevOps expertise, dedicated servers, and ongoing maintenance that pulls your team away from product work.
Building relevance, typo tolerance, faceting, and autocomplete from scratch takes months and rarely matches purpose-built search quality.
No SDK needed — one HTTP request from a small hook you own, rendered with your own components. Built for any React stack.
Paste a ~25-line useSearchJet hook (fetch + debounce + AbortController) and get full control over search state and UI. Wire it to any component, any design system.
Results come back as plain JSON (id, title, content, url, excerpt), so you render them with your own components. Nothing to override.
No SDK types to learn. Describe the response (results, total, limit, offset) with a small interface for your data shape.
One GET request with your search key. Add limit, offset, sort, or filters when you need them, but never forced to configure.
Works with Vite, Create React App, Next.js, Remix, Astro, or any React setup. Any HTTP client, everywhere.
No search library to ship: the browser's built-in fetch does the work. No bloat, no performance hit.
Two approaches — a simple search page or full control with filters
// useSearchJet.js — ~25 lines you paste into your app (not a package)
import { useEffect, useState } from 'react';
const API = 'https://app.searchjetengine.com/api/v1/search';
const SEARCH_KEY = 'your_search_api_key'; // search-only key; never use the private key here
export function useSearchJet(query, { limit = 10, filters } = {}) {
const [data, setData] = useState({ results: [], total: 0 });
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!query) { setData({ results: [], total: 0 }); return; }
const controller = new AbortController();
const timer = setTimeout(async () => {
setLoading(true);
const params = new URLSearchParams({ q: query, limit: String(limit) });
if (filters) params.set('filters', filters);
try {
const res = await fetch(`${API}?${params}`, {
headers: { Authorization: `Bearer ${SEARCH_KEY}` },
signal: controller.signal,
});
setData(await res.json()); // { results: [...hits], total, limit, offset, ... }
} catch {} // aborted or network error
setLoading(false);
}, 200); // debounce
return () => { clearTimeout(timer); controller.abort(); };
}, [query, limit, filters]);
return { hits: data.results ?? [], total: data.total ?? 0, loading };
}
// SearchPage.jsx
import { useState } from 'react';
import { useSearchJet } from './useSearchJet';
export default function SearchPage() {
const [query, setQuery] = useState('');
const { hits, total } = useSearchJet(query);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search anything..." />
{query && <p>{total} results</p>}
{hits.map((hit) => (
<article key={hit.id} className="p-4 border-b">
<h3><a href={hit.url}>{hit.title}</a></h3>
<p>{hit.excerpt}</p>
</article>
))}
</div>
);
}// SearchWithFilters.jsx — uses the useSearchJet hook from the left
import { useState } from 'react';
import { useSearchJet } from './useSearchJet';
const CATEGORIES = ['all', 'docs', 'blog', 'product'];
export function SearchWithFilters() {
const [query, setQuery] = useState('');
const [type, setType] = useState('all');
// "filters" only works on attributes marked filterable in your index
// settings; otherwise the API returns error code INVALID_FILTER.
const filters = type === 'all' ? undefined : `type = "${type}"`;
const { hits, total, loading } = useSearchJet(query, { limit: 20, filters });
return (
<div className="flex gap-8">
{/* Sidebar filters */}
<aside className="w-64">
{CATEGORIES.map((c) => (
<label key={c} className="block">
<input type="radio" checked={type === c} onChange={() => setType(c)} /> {c}
</label>
))}
</aside>
{/* Results */}
<main className="flex-1">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products..."
className="w-full p-3 border rounded-lg"
/>
{loading ? (
<p>Searching...</p>
) : (
<>
{query && <p>{total} results</p>}
{hits.map((hit) => (
<article key={hit.id} className="p-4 border-b">
<h3><a href={hit.url}>{hit.title}</a></h3>
<p>{hit.excerpt}</p>
</article>
))}
</>
)}
</main>
</div>
);
}Need a complete starter project?
View GitHub ExamplesIllustrative example of a React app adding hosted search. Your results will vary.
| Metric | Before | After | Impact |
|---|---|---|---|
| Time to Implement | Weeks | Days | Much faster launch |
| Bundle Impact | 55KB (Algolia) | None — built-in fetch | No SDK to ship |
| Search Latency | Slower on large datasets | <150ms | Faster results |
| Annual Costs | Usage-based (Algolia) | $0-228 (Free or Pro) | A fraction of the cost |
Everything you need to know about SearchJet for React
Join developers building exceptional search experiences with SearchJet
Already have an account? Log in