Add search to a Next.js app
Index your data with the tachyon-sdk on the server, and query it from a route handler or server action.
This walks through wiring Tachyon into a Next.js App Router project: index documents server-side, and expose search through a route handler your client components can call.
1. Run Tachyon
docker run -p 8108:8108 -v tachyon-data:/data adikeshri/tachyonSee Configuration before deploying this anywhere reachable by more than your own machine.
2. Install the SDK
npm install tachyon-sdk3. Create a collection and index your data
Do this once — from a script, a seed route, or wherever your data currently lives (a database, a CMS, static content).
// scripts/index-products.ts
import { Tachyon } from "tachyon-sdk";
const client = new Tachyon({
url: process.env.TACHYON_URL!,
apiKey: process.env.TACHYON_ADMIN_KEY!,
});
await client.collections.create({
name: "products",
fields: [
{ name: "title", type: "text" },
{ name: "description", type: "text" },
{ name: "brand", type: "keyword", facet: true },
{ name: "price", type: "int", filter: true, sort: true },
],
});
const products = await getProductsFromYourDatabase();
await client.collection("products").documents.index(
products.map((p) => ({
id: p.id,
title: p.title,
description: p.description,
brand: p.brand,
price: p.priceCents,
})),
);See Collections and Documents for the full schema and indexing shapes.
4. Expose search through a route handler
Query with the search key, not the admin key — the route handler is reachable from the client, so it should only be able to read, never write. See Configuration → Authentication.
// app/api/search/route.ts
import { NextRequest, NextResponse } from "next/server";
import { Tachyon } from "tachyon-sdk";
const client = new Tachyon({
url: process.env.TACHYON_URL!,
apiKey: process.env.TACHYON_SEARCH_KEY!,
});
export async function GET(req: NextRequest) {
const q = req.nextUrl.searchParams.get("q") ?? "";
if (!q) return NextResponse.json({ hits: [], found: 0 });
const results = await client.collection("products").search({
q,
queryBy: ["title", "description"],
facet: ["brand"],
limit: 20,
});
return NextResponse.json(results);
}5. Call it from a client component
"use client";
import { useState, useTransition } from "react";
export function ProductSearch() {
const [query, setQuery] = useState("");
const [results, setResults] = useState<{ document: Record<string, unknown> }[]>([]);
const [isPending, startTransition] = useTransition();
function onChange(value: string) {
setQuery(value);
startTransition(async () => {
const res = await fetch(`/api/search?q=${encodeURIComponent(value)}`);
const data = await res.json();
setResults(data.hits);
});
}
return (
<div>
<input value={query} onChange={(e) => onChange(e.target.value)} placeholder="Search products…" />
{isPending ? <span>Searching…</span> : null}
<ul>
{results.map((hit) => (
<li key={String(hit.document.id)}>{String(hit.document.title)}</li>
))}
</ul>
</div>
);
}Keeping the index in sync
Whenever a product is created, updated, or deleted in your database, call
client.collection('products').documents.index(...) or .delete(id) from
the same code path — a mutation route, a server action, a database trigger
via a queue. Tachyon has no built-in connector to your database, so keeping
search fresh is on your application, the same as with any external search
engine.
Next steps
- Filtering and Sorting for narrowing and ordering results beyond plain relevance
- Faceting for a filter sidebar backed by real counts
- Deployment for running Tachyon in production