Server-side search without hydration issues. Route-handler proxies, SSG caching, and edge-compatible fetch — the headless option for teams who own their UI.
Get your free API key — the same key your Next.js route handler uses server-side.
Enter your email and we'll inject a working key straight into the sandbox below. No credit card. 10,000 free searches/month.
This is what your Next.js route handler will return. Type a query below.
const params = new URLSearchParams({ q: 'wireless headphones', limit: '5' });
// Search as you type → instant results, no SDK needed
const res = await fetch(`https://app.searchjetengine.com/api/v1/search?${params}`, {
headers: { Authorization: 'Bearer 8e371cadae7aefedd3c99e41835df7ad709a27128c2a2d0c10ac408b1124c5a7' }
});
console.log(await res.json()); // ← live JSON below{
// Run a query above to see the live
// JSON response from the SearchJet API.
// Typo-tolerant, sub-150ms.
}Search keys live in a route handler (server-only), never in the client bundle. Your index and quota stay protected.
Fetch search results inside a React Server Component or ISR-revalidate them at build time. No client-side waterfall, no hydration mismatch.
Tell Next.js to cache responses with revalidate / cache tags. Cached responses skip the round trip entirely, and uncached SearchJet queries return in under 150ms.
// app/api/search/route.ts — server-only, key stays private
import { NextResponse } from 'next/server';
const SEARCHJET_API = 'https://app.searchjetengine.com/api/v1/search';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const q = searchParams.get('q')?.trim();
if (!q) return NextResponse.json({ error: 'Missing q' }, { status: 400 });
const res = await fetch(`${SEARCHJET_API}?q=${encodeURIComponent(q)}&limit=8`, {
headers: {
// Search key from .env (no NEXT_PUBLIC_ prefix). Never use the private key here.
Authorization: `Bearer ${process.env.SEARCHJET_API_KEY}`,
},
// Cache repeat queries — 60s revalidation
next: { revalidate: 60 },
});
const data = await res.json();
if (!res.ok) return NextResponse.json(data, { status: res.status });
// `results` is the array of hits; `total` is the number of matches
return NextResponse.json({ results: data.results, total: data.total });
}'use client';
import { useState, useEffect } from 'react';
type Hit = { id: string; title: string; url: string; excerpt?: string };
export default function SiteSearch() {
const [query, setQuery] = useState('');
const [hits, setHits] = useState<Hit[]>([]);
useEffect(() => {
const q = query.trim();
if (!q) { setHits([]); return; }
const controller = new AbortController();
const timer = setTimeout(async () => {
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, {
signal: controller.signal,
});
const data = await res.json();
setHits(data.results ?? []);
} catch (err) {
if ((err as Error).name !== 'AbortError') console.error(err);
}
}, 150); // debounce
return () => { clearTimeout(timer); controller.abort(); };
}, [query]);
return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search…"
/>
<ul>
{hits.map((hit) => (
<li key={hit.id}>
<a href={hit.url}>{hit.title}</a>
</li>
))}
</ul>
</>
);
}// app/search/page.tsx — React Server Component (Next.js 15+)
type Hit = { id: string; title: string; url: string; excerpt?: string };
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string }>;
}) {
const { q = '' } = await searchParams; // a Promise in Next.js 15+
let results: Hit[] = [];
let total = 0;
if (q) {
// Runs on the server — key never reaches the browser
const res = await fetch(
`https://app.searchjetengine.com/api/v1/search?q=${encodeURIComponent(q)}&limit=10`,
{
headers: { Authorization: `Bearer ${process.env.SEARCHJET_API_KEY}` },
next: { revalidate: 60 },
}
);
if (res.ok) {
const data = await res.json();
results = data.results; // array of hits
total = data.total;
}
}
return (
<main>
<form action="/search">
<input name="q" defaultValue={q} placeholder="Search…" />
</form>
{q && <p>{total} results for “{q}”</p>}
<ul>
{results.map((hit) => (
<li key={hit.id}>
<a href={hit.url}>{hit.title}</a>
</li>
))}
</ul>
</main>
);
}10,000 free searches/month. Built for the App Router.