> For the complete Lynkow documentation index in agent-friendly format, see [llms.txt](/llms.txt).

# Instant Search

**Publié le** : 2026-08-14
**Catégorie** : Features

Add full-text search with typo tolerance to your Next.js site. Lynkow Instant Search indexes all your published content and keeps the index in sync automatically -- you just query the SDK.

> **Reference** - This guide is a tutorial. For exhaustive, always-current signatures and field lists, see the auto-generated reference:
> 
> - SDK: [`SearchService`](/docs/sdk/services/sdk-search-service)
> - API: [Storefront: Search](/docs/storefront-api/storefront-search)

## Prerequisites

You should have the Lynkow SDK installed and a client initialized. If not, see [Guide 1: Quick Start](/docs/sdk/getting-started/quick-start).

```typescript
// lib/lynkow.ts
import { createClient } from 'lynkow'

export const lynkow = createClient({
  siteId: process.env.NEXT_PUBLIC_LYNKOW_SITE_ID!,
  fetchOptions: {
    next: { revalidate: 60 },
  },
})
```

---

## 1. Enable search

Go to your Lynkow admin dashboard: **Settings > SEO > Search** and toggle it on. This triggers a full index of all your published content. From that point on, every publish, update, archive, or delete is synced to the search index automatically.

---

## 2. Basic search

Use `lynkow.search.search()` to query from a Server Component, API route, or Client Component.

```typescript
import { lynkow } from '@/lib/lynkow'

const results = await lynkow.search.search('pagination')

results.data   // Array of matching articles
results.meta   // { total, page, totalPages, perPage, query, processingTimeMs }
```

### Filter by locale, category, or tag

```typescript
const results = await lynkow.search.search('formulaire', {
  locale: 'fr',
  category: 'guides',     // category slug
  tag: 'forms',           // tag slug
  page: 1,
  limit: 10,
})
```

### Typo tolerance

Lynkow Instant Search handles typos automatically. A query for `"pagniation"` will still find articles about pagination. No configuration needed.

---

## 3. Search result shape

Each hit in `results.data` contains the fields the examples below read:

```typescript
interface SearchHit {
  id: string              // Content UUID
  title: string           // Article title
  path: string            // Full URL path (e.g. "/docs/guides/forms")
  excerpt: string         // Short summary
  categories: Array<{ name: string; slug: string }>
  // ...plus slug, locale, type, tags, metaTitle, metaDescription,
  //    authorName, featuredImage, publishedAt, updatedAt, _formatted
}
```

See the full `SearchHit` shape in the [SDK Types reference](/docs/sdk/types).

The `_formatted` object contains the same fields but with matching terms wrapped in `<em>` tags for highlighting.

The `meta` object exposes pagination and timing:

```typescript
interface SearchMeta {
  total: number           // Total matching results
  page: number            // Current page
  totalPages: number      // Total pages
  processingTimeMs: number // Search engine response time
  // ...plus perPage, query
}
```

See the full `SearchMeta` shape in the [SDK Types reference](/docs/sdk/types).

---

## 4. Build a search page (Server Component)

A full search page with pagination using Server Components -- no client-side JavaScript needed.

```typescript
// app/search/page.tsx
import { lynkow } from '@/lib/lynkow'
import Link from 'next/link'
import type { SearchHit } from 'lynkow'

interface Props {
  searchParams: Promise<{ q?: string; page?: string }>
}

export default async function SearchPage({ searchParams }: Props) {
  const { q, page } = await searchParams

  if (!q) {
    return (
      <div>
        <h1>Search</h1>
        <form action="/search" method="get">
          <input type="search" name="q" placeholder="Search articles..." autoFocus />
          <button type="submit">Search</button>
        </form>
      </div>
    )
  }

  const results = await lynkow.search.search(q, {
    page: Number(page) || 1,
    limit: 20,
  })

  return (
    <div>
      <h1>Results for &ldquo;{q}&rdquo;</h1>
      <p>
        {results.meta.total} result{results.meta.total !== 1 ? 's' : ''} in{' '}
        {results.meta.processingTimeMs}ms
      </p>

      {results.data.length === 0 ? (
        <p>No results found. Try a different query.</p>
      ) : (
        <ul>
          {results.data.map((hit: SearchHit) => (
            <li key={hit.id}>
              <Link href={hit.path}>
                <h2>{hit.title}</h2>
                {hit.excerpt && <p>{hit.excerpt}</p>}
                {hit.categories.length > 0 && (
                  <span>{hit.categories.map((c) => c.name).join(', ')}</span>
                )}
              </Link>
            </li>
          ))}
        </ul>
      )}

      {results.meta.totalPages > 1 && (
        <nav>
          {results.meta.page > 1 && (
            <Link href={`/search?q=${q}&page=${results.meta.page - 1}`}>Previous</Link>
          )}
          {results.meta.page < results.meta.totalPages && (
            <Link href={`/search?q=${q}&page=${results.meta.page + 1}`}>Next</Link>
          )}
        </nav>
      )}
    </div>
  )
}
```

---

## 5. Build a search box with autocomplete (Client Component)

For real-time search-as-you-type, use a Client Component that calls the SDK with a debounce.

```typescript
'use client'

import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import type { SearchHit } from 'lynkow'

// Import your SDK client -- must be available client-side
const SITE_ID = process.env.NEXT_PUBLIC_LYNKOW_SITE_ID!
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://search.lynkow.com'

export function SearchBox() {
  const [query, setQuery] = useState('')
  const [hits, setHits] = useState<SearchHit[]>([])
  const [isOpen, setIsOpen] = useState(false)
  const router = useRouter()

  useEffect(() => {
    if (!query.trim()) {
      setHits([])
      return
    }

    const timer = setTimeout(async () => {
      const res = await fetch(
        `${API_URL}/storefront/${SITE_ID}/search?q=${encodeURIComponent(query)}&limit=5`
      )
      const json = await res.json()
      setHits(json.data || [])
      setIsOpen(true)
    }, 200) // 200ms debounce

    return () => clearTimeout(timer)
  }, [query])

  return (
    <div style={{ position: 'relative' }}>
      <input
        type="search"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onFocus={() => hits.length > 0 && setIsOpen(true)}
        onKeyDown={(e) => {
          if (e.key === 'Enter' && query.trim()) {
            router.push(`/search?q=${encodeURIComponent(query)}`)
            setIsOpen(false)
          }
        }}
        placeholder="Search..."
      />

      {isOpen && hits.length > 0 && (
        <ul
          style={{
            position: 'absolute',
            top: '100%',
            left: 0,
            right: 0,
            background: 'white',
            border: '1px solid #e5e7eb',
            borderRadius: 8,
            boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
            listStyle: 'none',
            padding: 0,
            margin: '4px 0 0',
            zIndex: 50,
          }}
        >
          {hits.map((hit) => (
            <li key={hit.id}>
              <a
                href={hit.path}
                onClick={() => setIsOpen(false)}
                style={{ display: 'block', padding: '8px 12px', textDecoration: 'none' }}
              >
                <strong>{hit.title}</strong>
                {hit.categories.length > 0 && (
                  <span style={{ color: '#6b7280', fontSize: '0.875rem', marginLeft: 8 }}>
                    {hit.categories[0].name}
                  </span>
                )}
              </a>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}
```

This component calls the public search API directly from the browser -- no SDK import needed client-side. The `search.lynkow.com` endpoint handles CORS automatically.

---

## 6. LLM and AI agent access

When search is enabled, Lynkow automatically does three things that make your content searchable by AI:

### llms.txt integration

Your `llms.txt` file gets a new `## Search` section with the full endpoint URL, parameters, and an example query. Any LLM that reads your llms.txt (ChatGPT, Claude, Perplexity) learns how to search your content instead of crawling every page.

```markdown
## Search

This site has a full-text search API with typo tolerance.

**Endpoint:**
GET https://search.lynkow.com/storefront/{siteId}/search?q={query}

**Parameters:** q (required), locale, category, tag, page, limit (max 100)
```

### Public search API

The search endpoint requires no authentication. Any HTTP client can call it:

```
GET https://search.lynkow.com/storefront/{siteId}/search?q=pagination&locale=en&category=guides&limit=10
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `q` | string | required | Search query |
| `locale` | string | all | Filter by locale code |
| `category` | string | all | Filter by category slug |
| `tag` | string | all | Filter by tag slug |
| `page` | number | 1 | Page number |
| `limit` | number | 20 | Results per page (max 100) |


Response format:

```json
{
  "data": [
    {
      "id": "uuid",
      "title": "Dynamic Forms",
      "slug": "forms",
      "path": "/guides/forms",
      "excerpt": "How to build forms with Lynkow",
      "categories": [{ "name": "Guides", "slug": "guides" }],
      "publishedAt": 1712505600
    }
  ],
  "meta": {
    "total": 12,
    "page": 1,
    "totalPages": 1,
    "perPage": 20,
    "query": "pagination",
    "processingTimeMs": 3
  }
}
```

### MCP tool

AI agents using Lynkow's MCP server get a `search_site_contents` tool that wraps the search API. This lets tools like Claude Code or Cursor search your documentation without parsing HTML.

---

## 7. Multi-language search

Use the `locale` parameter to search within a specific language:

```typescript
// French content only
const results = await lynkow.search.search('formulaire', { locale: 'fr' })

// English content only
const results = await lynkow.search.search('form', { locale: 'en' })

// All languages (default)
const results = await lynkow.search.search('form')
```

---

## 8. Testing

### Verify search is enabled

```bash
curl -s "https://search.lynkow.com/storefront/YOUR_SITE_ID/search?q=test"
```

A 503 response means search is not enabled for this site. Enable it in **Settings > SEO > Search**.

### Test typo tolerance

```bash
curl -s "https://search.lynkow.com/storefront/YOUR_SITE_ID/search?q=pagniation"
# Should still find articles about "pagination"
```

### Test filters

```bash
# By category
curl -s "https://search.lynkow.com/storefront/YOUR_SITE_ID/search?q=test&category=guides"

# By locale
curl -s "https://search.lynkow.com/storefront/YOUR_SITE_ID/search?q=test&locale=fr"
```

---

## Summary

| What | How | Details |
| --- | --- | --- |
| Enable search | Admin: Settings > SEO > Search | One toggle, auto-indexes all content |
| Server-side search | `lynkow.search.search(query, options)` | Returns typed `SearchResponse` |
| Client-side autocomplete | `fetch()` to `/storefront/{siteId}/search` | No SDK needed client-side |
| LLM discovery | Automatic via `llms.txt` | Search section added when enabled |
| MCP tool | `search_site_contents` | For AI agents (Claude Code, Cursor) |
| Sync | Automatic | Publish/update/archive/delete synced in real-time |