# Add search to a Next.js app
URL: /docs/guides/nextjs

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 [#1-run-tachyon]

```bash
docker run -p 8108:8108 -v tachyon-data:/data adikeshri/tachyon
```

See [Configuration](/docs/configuration#authentication) before deploying
this anywhere reachable by more than your own machine.

## 2. Install the SDK [#2-install-the-sdk]

```bash
npm install tachyon-sdk
```

## 3. Create a collection and index your data [#3-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).

```ts
// 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](/docs/collections) and [Documents](/docs/documents) for
the full schema and indexing shapes.

## 4. Expose search through a route handler [#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](/docs/configuration#authentication).

```ts
// 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 [#5-call-it-from-a-client-component]

```tsx
"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 [#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 [#next-steps]

* [Filtering](/docs/filtering) and [Sorting](/docs/sorting) for narrowing
  and ordering results beyond plain relevance
* [Faceting](/docs/faceting) for a filter sidebar backed by real counts
* [Deployment](/docs/deployment) for running Tachyon in production
