SEO and head
Set page titles and meta tags, extend the engine's defaults, and redirect old URLs.
Framework: TanStack Start
This page is written for TanStack Start, the default and currently supported framework. See what depends on the framework.
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
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.
In a route file you connect it with withHead (the head line of a route):
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
Pass a second argument to adjust the result. It receives the engine's head, the request context and the loader data:
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:
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
withHead accepts any object with a head(ctx, data) function that returns a head description. Write one for a page you added:
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.
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 |
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.
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:
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.
Next: Salla SDK and events, to change the cart and wishlist in the browser.