# Search (https://docs.salla.dev/twilight/react/routes/search)



<Callout title="Framework: TanStack Start">
  This page is written for TanStack Start, the default and currently supported framework. [See what depends on the framework](https://docs.salla.dev/twilight/react/start/frameworks).
</Callout>

The customer sees the products that match their keyword, in the same list layout as a [category](https://docs.salla.dev/twilight/react/routes/category). Customise this route to show a loading state when the keyword changes, or change the head tags.

## URL [#url]

| Pattern              | Example                                  |
| -------------------- | ---------------------------------------- |
| `/{-$locale}/search` | `/search?q=shirt` · `/ar/search?q=shirt` |

Optional param: `locale` ([locale prefix](https://docs.salla.dev/twilight/react/routes/home#locale-prefix)).

### Search params [#search-params]

| Name   | Type     | Default | Description                                                                                                                                   |
| ------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `q`    | `string` | `''`    | The keyword. A missing value becomes `''`, and a number (`?q=123`) becomes a string.                                                          |
| `sort` | `string` | none    | The sort order chosen in the sort menu.                                                                                                       |
| `page` | `number` | `1`     | Left out of the URL on page 1. The API pages only by its own cursor, so the loader ignores a number and the list starts from the first batch. |

### Try it [#try-it]

Change the keyword and the language, or switch to a phone width. This is the real search route running against the demo store.

<RoutePreview path="/search?q={q}" pattern="/{-$locale}/search" controls="{ q: { label: 'Keyword (q)', default: 'light', placeholder: 'light' } }" />

## Route file [#route-file]

The engine writes this file into its own `.twilight/` folder, not into `app/routes/`. Search has its own template: it reads `q`, `sort` and `page` from the URL.

```tsx title="search.tsx (served by the engine)"
// @auto-generated
import { createFileRoute } from '@tanstack/react-router';
import {
  ProductListing,
  searchListingSearch,
} from '@salla.sa/twilight-theme-engine/routes/product-listing';
import type { ProductListLoaderData } from '@salla.sa/twilight-theme-engine/routes/product-listing';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/search')({
  ...searchListingSearch,
  loader: ({ deps, params }): Promise<ProductListLoaderData> =>
    ProductListing.loader({
      params: { source: 'search' },
      search: deps,
      locale: params.locale,
    }),
  head: withHead(ProductListing),
  pendingComponent: () => <ProductListing.Pending />,
  component: ProductListingComponent,
});

function ProductListingComponent() {
  const data: ProductListLoaderData = Route.useLoaderData();
  return <ProductListing.Component {...data} />;
}
```

`ProductListing` is shared with [category](https://docs.salla.dev/twilight/react/routes/category), [brand](https://docs.salla.dev/twilight/react/routes/brand), [tag](https://docs.salla.dev/twilight/react/routes/tag) and [products](https://docs.salla.dev/twilight/react/routes/products). To replace only this route, add `app/routes/search.tsx` without the `// @auto-generated` line, and the build uses yours. The build stops updating a file you own, so spread `searchListingSearch` into it and pass `search: deps` to the loader rather than copying the URL handling: `?q=`, `?sort=` and `?page=` then keep reaching the loader as the engine changes them. To restyle every product list at once, register a page as `product:list-page` instead; see [Replace the product list page](https://docs.salla.dev/twilight/react/recipes/replace-the-product-card#replace-the-whole-product-list-page).

### A customised version [#a-customised-version]

This keeps the same search params, shows a product-grid skeleton while the loader re-runs, and asks search engines not to index result pages.

```tsx title="app/routes/search.tsx"
import { createFileRoute } from '@tanstack/react-router';
import {
  ProductListing,
  searchListingSearch,
} from '@salla.sa/twilight-theme-engine/routes/product-listing';
import type { ProductListLoaderData } from '@salla.sa/twilight-theme-engine/routes/product-listing';
import { ProductGridSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/search')({
  ...searchListingSearch,
  loader: ({ deps, params }): Promise<ProductListLoaderData> =>
    ProductListing.loader({
      params: { source: 'search' },
      search: deps,
      locale: params.locale,
    }),
  head: withHead(ProductListing, (result) => ({
    ...result,
    robots: 'noindex, follow',
  })),
  pendingComponent: () => <ProductGridSkeleton />,
  component: ListComponent,
});

function ListComponent() {
  const data: ProductListLoaderData = Route.useLoaderData();
  return <ProductListing.Component {...data} />;
}
```

## What it loads [#what-it-loads]

`ProductListLoaderData`, shared by every product list:

| Field        | Description                                        |
| ------------ | -------------------------------------------------- |
| `page`       | `title` (the keyword), `slug` and `breadcrumbs`.   |
| `source`     | `{ type: 'search', value: keyword }`.              |
| `query`      | `sort` (default `ourSuggest`) and `filters: true`. |
| `products`   | The first batch of products.                       |
| `pagination` | `next`: the cursor for the next batch, or `null`.  |
| `filters`    | The filters for the filter panel.                  |

The loader trims `q` and requests the matching products on the server. More products load in the browser as the customer scrolls. See [Loading data](https://docs.salla.dev/twilight/react/guide/loading-data).

## Head and SEO [#head-and-seo]

Title is the keyword and description is `Browse <keyword>`, with matching Open Graph and Twitter tags and hreflang links. There is no canonical tag.

## Edge cases [#edge-cases]

* **Empty `q`.** No request is made. The page shows the empty state with the translated "Search Results" title.
* **No matches.** The same empty state, titled with the keyword.
* **Sort and page.** The route keeps `q`, `sort` and `page` in the URL, so the sort menu works. A reload starts the list from the first batch again, because `page` is not a cursor.
* **Request failure.** There is no 404 for search: a failed request shows the error page.
* **Locale.** See [locale prefix](https://docs.salla.dev/twilight/react/routes/home#locale-prefix).

## See also [#see-also]

* [Category page](https://docs.salla.dev/twilight/react/routes/category)
* [Languages and RTL](https://docs.salla.dev/twilight/react/guide/languages-and-rtl)
* [Fork a built-in route](https://docs.salla.dev/twilight/react/recipes/fork-a-built-in-route)
* [Routes overview](https://docs.salla.dev/twilight/react/routes)
