> ## Documentation Index
> Fetch the complete documentation index at: https://docs.glood.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Search Page

> Build a full search results page — query, facets, sort, pagination and attribution — with the Glood Hydrogen Search SDK

A search results page is a [`useSearch`](/for-developers/glood-hydrogen-sdk/api-reference/search-api#usesearch)
call driven by the shopper's query, with facets, sort, pagination and attribution.

<Note>
  Prerequisite: the search app is registered (`.use(search())`), your app is wrapped in
  `<GloodProvider>`, and `https://search.glood.ai` is in your CSP `connectSrc`. See
  [Installation](/for-developers/glood-hydrogen-sdk/installation).
</Note>

## 1. Run the search

Read the query from the URL and pass it to
[`useSearch`](/for-developers/glood-hydrogen-sdk/api-reference/search-api#usesearch). Set
`includeFacets` to get inline facet counts for the current query.

```tsx theme={null}
// app/routes/search.tsx
import { useState } from 'react';
import { useSearchParams } from 'react-router';
import { useSearch } from '@glood/hydrogen';

export default function SearchPage() {
  const [params] = useSearchParams();
  const query = params.get('q') ?? '';
  const [page, setPage] = useState(1);
  const [sort, setSort] = useState('relevance');

  const { products, data, loading, error, trackResultClick, trackFilter } = useSearch({
    query,
    sort,
    pagination: { page, limit: 24 },
    options: { includeFacets: true },
  });

  if (error) return <p>Search failed: {error.message}</p>;
  // …render below
}
```

Params are a [`SearchParams`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchparams--searchresponse);
`data` is a [`SearchResponse`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchparams--searchresponse)
and `products` are [`SearchProduct`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchproduct) objects.

## 2. Render results, sort and pagination

```tsx theme={null}
return (
  <>
    <select value={sort} onChange={(e) => { setSort(e.target.value); setPage(1); }}>
      <option value="relevance">Relevance</option>
      <option value="price_asc">Price: low to high</option>
      <option value="price_desc">Price: high to low</option>
      <option value="newest">Newest</option>
    </select>

    <p>{data?.pagination.total} results for “{query}”</p>

    <div className="grid">
      {products.map((p) => (
        <a
          key={p.product_id}
          href={`/products/${p.handle}`}
          onClick={() =>
            trackResultClick({
              request_id: data?.request_id,
              search_term: query,
              search_type: 'text',
              source: 'search_page',
              products: [{ product_id: p.product_id }],
            })
          }
        >
          <img src={p.image?.url} alt={p.title} loading="lazy" />
          <span>{p.title}</span>
          <span>{p.display_price ?? p.price}</span>
        </a>
      ))}
    </div>

    <nav>
      <button disabled={!data?.pagination.has_prev_page} onClick={() => setPage((p) => p - 1)}>
        Previous
      </button>
      <span>{data?.pagination.page} / {data?.pagination.total_pages}</span>
      <button disabled={!data?.pagination.has_next_page} onClick={() => setPage((p) => p + 1)}>
        Next
      </button>
    </nav>
  </>
);
```

`sort` values are listed in [`SearchSort`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchview--searchsort);
`data.pagination` is a [`SearchPagination`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchpagination).

## 3. Add facet filtering

Track selected facet values in state and pass them as the `facets` param (an array of
[`SearchAppliedFacet`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchparams--searchresponse)).
Render the buckets from `data.facets`:

```tsx theme={null}
const [selected, setSelected] = useState<Record<string, string[]>>({});

const { data } = useSearch({
  query,
  facets: Object.entries(selected).map(([key, value]) => ({ key, operator: 'or', value })),
  pagination: { page, limit: 24 },
  options: { includeFacets: true },
});

{data?.facets?.map((facet) => (
  <fieldset key={facet.key}>
    <legend>{facet.key}</legend>
    {facet.aggregation.buckets.map((b) => (
      <label key={b.value}>
        <input
          type="checkbox"
          checked={b.selected}
          onChange={() => {
            const cur = selected[facet.key] ?? [];
            const next = b.selected ? cur.filter((v) => v !== b.value) : [...cur, b.value];
            setSelected({ ...selected, [facet.key]: next });
            setPage(1);
            trackFilter({ search_term: query, filters: [{ key: facet.key, value: next }] });
          }}
        />
        {b.value} ({b.count})
      </label>
    ))}
  </fieldset>
))}
```

<Note>
  For stable filter definitions independent of the query, fetch them with
  [`fetchFilters`](/for-developers/glood-hydrogen-sdk/api-reference/search-api#fetchfilters), and
  for standalone live counts use
  [`fetchFilterAggregations`](/for-developers/glood-hydrogen-sdk/api-reference/search-api#fetchfilteraggregations).
</Note>

## 4. Attribution

[`useSearch`](/for-developers/glood-hydrogen-sdk/api-reference/search-api#usesearch) returns
[`trackSearch`](/for-developers/glood-hydrogen-sdk/api-reference/search-api#usesearch),
`trackResultView`, `trackResultClick`, `trackAddToCart` and `trackFilter`. Call `trackSearch`
when the results render, `trackResultClick` on click (step 2), and `trackAddToCart` on add.
Each takes a [`SearchTrack`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchtrack).
Standard Shopify events (`search_submitted`, `product_viewed`, …) are forwarded automatically
— see [Events & attribution](/for-developers/glood-hydrogen-sdk/search#events--attribution).

## 5. (Optional) Server-render the first page

To ship results in the initial HTML, call
[`fetchSearchResults`](/for-developers/glood-hydrogen-sdk/api-reference/search-api#fetchsearchresults)
in the route loader instead of the hook:

```tsx theme={null}
import { fetchSearchResults } from '@glood/hydrogen';

export async function loader({ request, context }) {
  const query = new URL(request.url).searchParams.get('q') ?? '';
  const results = await fetchSearchResults(
    { apiKey: context.env.GLOOD_API_KEY, myShopifyDomain: 'your-store.myshopify.com' },
    { query, pagination: { page: 1, limit: 24 } },
  ).catch(() => null); // never let search break the page
  return { query, results };
}
```

## See also

* [Instant Search](/for-developers/glood-hydrogen-sdk/guides/instant-search) — the type-ahead box that feeds this page
* [Search API Reference](/for-developers/glood-hydrogen-sdk/api-reference/search-api)
* [Full example route](/for-developers/glood-hydrogen-sdk/examples/search)
