# Category page (https://docs.salla.dev/twilight/react/routes/category)



<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 of one category, can sort them, and can narrow them with the store's filters. Customise this route to add content above the list, change the title, or render the list with your own toolbar. The real theme does the last one.

## URL [#url]

| Pattern                  | Example           |
| ------------------------ | ----------------- |
| `/$slug/c{$id}`          | `/shirts/c456`    |
| `/{locale}/$slug/c{$id}` | `/en/shirts/c456` |

### Params [#params]

| Name     | Type                  | Example  | Description                                                                           |
| -------- | --------------------- | -------- | ------------------------------------------------------------------------------------- |
| `slug`   | `string`              | `shirts` | For readable links. The route file does not pass it to the loader, so any slug works. |
| `id`     | `string`              | `456`    | The category id, written `c456` in the URL.                                           |
| `locale` | `string \| undefined` | `en`     | Optional ([locale prefix](https://docs.salla.dev/twilight/react/routes/home#locale-prefix)).                               |

### Search params [#search-params]

| Name   | Type     | Example    | Description                                                                                                                                               |
| ------ | -------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sort` | `string` | `bestSell` | Optional. The loader uses `ourSuggest` when absent.                                                                                                       |
| `page` | `number` | `2`        | Optional. Values of 1 or less are dropped. 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]

This is a category as the engine renders it, running against the demo store. Switch the language or the width. Change the id to open another one.

<RoutePreview path="/c/c{id}" pattern="/{locale}/$slug/c{$id}" controls="{ id: { label: 'Category id', default: '708738657' } }" />

## Route file [#route-file]

The engine writes this file into its own `.twilight/` folder, not into `app/routes/`:

```tsx title="$slug.c$id.tsx (served by the engine)"
// @auto-generated
import { createFileRoute } from '@tanstack/react-router';
import { ProductListing, listingSearch } 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}/$slug/c{$id}')({
  ...listingSearch,
  loader: ({ deps, params }): Promise<ProductListLoaderData> =>
    ProductListing.loader({
      params: { id: params.id },
      search: deps,
      locale: params.locale,
    }),
  head: withHead(ProductListing),
  pendingComponent: () => <ProductListing.Pending />,
  component: ProductListingComponent,
});

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

To take the page over, add `app/routes/$slug.c$id.tsx` without the `// @auto-generated` line, or write your own file and map the path in `app/routes.ts`: `route('/$slug/c{$id}', 'product-list.tsx')`. See [Fork a built-in route](https://docs.salla.dev/twilight/react/recipes/fork-a-built-in-route). 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 adds a suffix to the title and a line above the engine list.

```tsx title="app/routes/product-list.tsx"
import { createFileRoute } from '@tanstack/react-router';
import {
  ProductListing,
  listingSearch,
  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}/$slug/c{$id}')({
  ...listingSearch,
  loader: ({ deps, params }): Promise<ProductListLoaderData> =>
    ProductListing.loader({
      params: { id: params.id },
      search: deps,
      locale: params.locale,
    }),
  head: withHead(ProductListing, (head, _ctx, data) => ({
    ...head,
    title: `${data.page.title} | Official store`,
  })),
  component: ListComponent,
});

function ListComponent() {
  const data: ProductListLoaderData = Route.useLoaderData();
  return (
    <>
      <p className="container">{data.products.length} products on this page</p>
      <ProductListing.Component {...data} />
    </>
  );
}
```

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

`ProductListing.loader` is shared by every product list (category, tag, brand, search and the [products](https://docs.salla.dev/twilight/react/routes/products) lists). It returns `ProductListLoaderData`:

| Field        | Type                      | Description                                                        |
| ------------ | ------------------------- | ------------------------------------------------------------------ |
| `page`       | `ProductListPageMeta`     | `title`, `slug`, `id` and `breadcrumbs` (Home, then the category). |
| `source`     | `ProductListSourceConfig` | `type`, `value` (the id) and `entity` (the loaded `Category`).     |
| `query`      | `ProductListQuery`        | `sort` and `filters` (whether the filter panel may show).          |
| `products`   | `Product[]`               | The first batch of products.                                       |
| `pagination` | `RoutePagination`         | `next`: the cursor for the next batch, `null` at the end.          |
| `filters`    | `Filter[] \| undefined`   | Set for categories and search only.                                |

The loader fetches the category and its first products together on the server. The page then loads more products in the browser as the customer scrolls, and re-queries the list when a filter changes. Sort values are `ourSuggest`, `bestSell`, `topRated`, `priceFromLowToTop` and `priceFromTopToLow`. See [Loading data](https://docs.salla.dev/twilight/react/guide/loading-data).

[Hook slots](https://docs.salla.dev/twilight/react/guide/hooks-and-slots) on every product list: `product:list.start`, `product:list.items.start`, `product:list.items.end`, `product:list.end`.

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

Title is the category name, description is `Browse {name}`, canonical and `og:url` are the category's own URL, and the category image is the social image. Hreflang links cover each store language. `?sort` and `?page` do not change the canonical.

## Edge cases [#edge-cases]

* **Unknown category.** If a request fails or the category comes back empty, the loader throws a not-found error and the [not-found page](https://docs.salla.dev/twilight/react/routes/not-found) renders.
* **Wrong slug.** No redirect; the canonical tag keeps search engines on the right URL.
* **Filters.** The panel shows only when the store has product filters enabled. Applying a filter updates the address bar and the list is re-queried in the browser, so server-rendered HTML never contains filtered results.
* **Empty category.** The list shows its empty state. There is no 404.
* **Invalid `sort`.** It is passed through as is; the sort menu then has no selected entry.

## See also [#see-also]

* [Brand page](https://docs.salla.dev/twilight/react/routes/brand), [Tag page](https://docs.salla.dev/twilight/react/routes/tag), [Search](https://docs.salla.dev/twilight/react/routes/search), [Products](https://docs.salla.dev/twilight/react/routes/products)
* [Replace the product card](https://docs.salla.dev/twilight/react/recipes/replace-the-product-card)
* [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)
