# SEO and head (https://docs.salla.dev/twilight/react/guide/seo-and-head)



<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>

Read this when you want to control how a page appears in search results and share previews.

The **head** is the part of the page the browser does not show but search engines and social previews read:
the title, description, share image and language alternates. The engine fills it for every built-in page.

## What you get for free [#what-you-get-for-free]

Each built-in page module has a `head` function. For the cart it sets the title and description from the page data
and adds `hreflang` links, which tell search engines about the page in other languages. The root route also sets store-wide defaults:
title, description, Open Graph and Twitter tags from the store's settings.

Your `__root.tsx` writes these tags into `<head>` with `ThemeHeadContent` from `@salla.sa/twilight-theme-engine/tanstack`. It is TanStack Router's `HeadContent` without the `modulepreload` links, which compete with the page's main image; use it in place of `HeadContent`, not next to it. See [Performance](https://docs.salla.dev/twilight/react/guide/performance#paint-the-first-screen-first).

In a route file you connect it with `withHead` (the `head` line of a [route](https://docs.salla.dev/twilight/react/guide/routes-and-pages)):

```tsx title="app/routes/cart-page.tsx"
import { createFileRoute } from '@tanstack/react-router';
import { Cart } from '@salla.sa/twilight-theme-engine/routes/cart';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/cart')({
  loader: ({ params }) => Cart.loader({ locale: params.locale }),
  head: withHead(Cart),
  // …
});
```

## Change the engine's head [#change-the-engines-head]

Pass a second argument to adjust the result. It receives the engine's head, the request context and the loader data:

```tsx
head: withHead(Product, (result) => ({ ...result, robots: 'noindex, follow' }))
```

`withHead` already adds the store name to the title (`Page title - Store name`). Set `appendStoreTitle: false` in the result when your title already names the store.

The product listings (`/products`, `/search`, a category, a tag, a brand's products) and `/reviews` have no file in your `app/routes/`: the engine serves them from its own copy. To change the head of one, copy the engine's file into `app/routes/` (for search, `node_modules/@salla.sa/twilight-theme-engine/.twilight/search.tsx`, written by the first `pnpm dev` or build), delete its `// @auto-generated` first line, and change only the `head` line:

```tsx title="app/routes/search.tsx"
head: withHead(ProductListing, (result) => ({ ...result, robots: 'noindex, follow' })),
```

A file without that line is yours, and the engine uses it instead of its own.

## Head for your own page [#head-for-your-own-page]

`withHead` accepts any object with a `head(ctx, data)` function that returns a head description. Write one for a page you added:

```tsx title="app/routes/faq.tsx"
import { createFileRoute } from '@tanstack/react-router';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
import { buildHreflangAlternates } from '@salla.sa/twilight-theme-engine/utils';

export const Route = createFileRoute('/{-$locale}/faq')({
  loader: () => ({}),
  head: withHead({
    head: (ctx) => ({
      title: 'Frequently asked questions',
      description: 'Answers about shipping and returns.',
      alternateLanguages: buildHreflangAlternates(ctx.settings, ctx.location.pathname),
    }),
  }),
  component: FaqPage,
});
```

`buildHreflangAlternates` (from `@salla.sa/twilight-theme-engine/utils`) returns the language links for a path, the same ones the engine's pages get. On a store served from a shared Salla address such as `demostore.salla.sa/<username>`, where the URL carries no language, each link asks for its language with `?lang=` instead of a path segment.

A head description accepts:

| Field                                        | Becomes                                                                                                             |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `title`, `description`, `keywords`, `robots` | The matching tags                                                                                                   |
| `canonical`                                  | `<link rel="canonical">`. Without one, `withHead` uses the page's own path on the store's domain, without the query |
| `openGraph`, `twitter`                       | Share-card tags (title, images, url, locale …)                                                                      |
| `alternateLanguages`                         | `hreflang` links                                                                                                    |
| `jsonLd`                                     | A structured-data script                                                                                            |
| `meta`, `links`, `styles`, `scripts`         | Anything else you need                                                                                              |

<Callout type="warn">
  `withHead` adds nothing when the route has no loader data, which is why the page above has a `loader`. Even an empty one is enough.
</Callout>

## Redirects [#redirects]

A loader can send the customer somewhere else by throwing a redirect. The starter theme has a route that turns an old
brand URL into the new one, using an engine helper:

```tsx title="app/routes/$slug.brand-$id.tsx"
import { slugBrandRedirectLoader } from '@salla.sa/twilight-theme-engine/routes/seo-redirects';

export const Route = createFileRoute('/{-$locale}/$slug/brand-{$id}')({
  loader: ({ params }) =>
    slugBrandRedirectLoader({
      params: { slug: params.slug, id: params.id, locale: params.locale },
    }),
});
```

It sends `/{slug}/brand-12` to `/{locale}/brands/12` and keeps the language.
For your own redirects, throw TanStack Router's `redirect` from a loader. The engine's other redirect helpers are on [SEO redirects](https://docs.salla.dev/twilight/react/routes/seo-redirects).

**Next:** [Salla SDK and events](https://docs.salla.dev/twilight/react/guide/salla-sdk-and-events), to change the cart and wishlist in the browser.
