Skip to content
Twilight React Playground
ثيم رائدaren

buildHreflangAlternates

functionAdvancedserverbrowserlive demo

Builds the hreflang links that tell search engines the same page exists in each store language, plus an x-default.

import { buildHreflangAlternates } from '@salla.sa/twilight-theme-engine/utils/baseHead';

In plain words

A multilingual store serves the same page at /ar/cart and /en/cart. Search engines should know these are one page in two languages, not two pages. buildHreflangAlternates(settings, path) returns one { hreflang, href } per store language, plus x-default for visitors in other languages, ready for alternateLanguages in a HeadDescriptor.

Signature

function buildHreflangAlternates(
  settings: StoreContext | null | undefined,
  path?: string   // a router path or a full URL; a two-letter first segment is dropped
): Array<{ hreflang: string; href: string }> | undefined

Try it live

One link per store language for the same page, plus x-default, built from the store URL and the path you pass.Try this: type /tv: a two-letter first segment is taken for a language and dropped. Then paste a category URL from the API, username included.
Storefront canvas · en · LTR
x-defaulthttps://demostore.salla.sa/ar/cart
arhttps://demostore.salla.sa/ar/cart
enhttps://demostore.salla.sa/en/cart
Controls
A router path (with or without the locale) or a full URL.
What a theme writes
import { buildHreflangAlternates } from '@salla.sa/twilight-theme-engine/utils/baseHead';
import type { HeadDescriptor } from '@salla.sa/twilight-theme-engine/utils/head';
import type { TwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';

// A route module's head(): ctx.location.pathname is '/ar/cart' here.
export function head(ctx: TwilightContext): HeadDescriptor {
  return {
    title: 'Lookbook',
    alternateLanguages: buildHreflangAlternates(ctx.settings, ctx.location.pathname),
  };
}

Example

app/routes/lookbook.head.ts
import type { TwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';
import { buildHreflangAlternates } from '@salla.sa/twilight-theme-engine/utils/baseHead';
import type { HeadDescriptor } from '@salla.sa/twilight-theme-engine/utils/head';

// A route module's head(ctx, data), as every engine route writes it.
export function head(ctx: TwilightContext): HeadDescriptor {
  return {
    title: 'Lookbook',
    alternateLanguages: buildHreflangAlternates(ctx.settings, ctx.location.pathname),
  };
}

How it behaves

  • Every engine route module's head() calls it with ctx.location.pathname; the product listing passes the category or tag URL when there is one.

  • A full URL is cut to its pathname. The first segment is dropped when it has exactly two characters, and the rest is appended to <origin of store.url>/<language code>. The root path gives …/ar, with no trailing slash.

  • x-default points at the ar version when the store has Arabic, otherwise at its first language.

  • Languages come from settings.languages. Under TanStack each item becomes <link rel="alternate" hrefLang=… href=…>.

Gotchas

  • It returns undefined when the store lists no languages.

  • Any two-character first segment is treated as a locale, not only real ones: /tv/p1 builds …/ar/p1.

  • Only the origin of store.url is used. For a store on a shared host, router paths lose the username: on the demo store /ar/cart gives https://demostore.salla.sa/en/cart, an address of no store. A full URL from the API keeps the username, because it is still in the path.

  • docs/03-routing-system.md and docs/07-data-types.md call it as buildHreflangAlternates({ store, canonicalUrl }). The real signature is (settings, path).

Related

Source and docs