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

mergeHead

functionAdvancedserverbrowserlive demo

Lays additions over a base HeadDescriptor: tag lists are appended, Open Graph and Twitter merged one level, every other field replaced.

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

In plain words

A page often wants an existing head plus a few changes: another title, one more meta tag. mergeHead(base, extension) returns a new descriptor with the extension laid over the base. Neither argument is changed.

Signature

function mergeHead(base: HeadDescriptor, extension: Partial<HeadDescriptor>): HeadDescriptor

Try it live

The base is this store's defaults from buildBaseHead(). The extension is what a page adds on top.Try this: set title to undefined: the store name disappears instead of staying. Turn on the image: openGraph.title survives, openGraph.images is replaced.
Storefront canvas · en · LTR

title: "Summer sale" · openGraph.title: "ثيم رائد" · openGraph.images: "https://cdn.salla.network/salla.com/logo-wide-1.svg" · meta: 0

mergeHead(base, extension): {…} 12 keys
title: "Summer sale"
description: "<p>This demo store allows you to explore the look and design of stores on the Salla platform. Browse the categories, try the shopping experience, and preview t…"
canonical: "https://demostore.salla.sa"
openGraph: {…} 7 keys
type: "website"
siteName: "ثيم رائد"
title: "ثيم رائد"
description: "<p>This demo store allows you to explore the look and design of stores on the Salla platform. Browse the categories, try the shopping experience, and preview t…"
url: "https://demostore.salla.sa"
locale: "en_US"
images: "https://cdn.salla.network/salla.com/logo-wide-1.svg"
twitter: {…} 5 keys
card: "summary_large_image"
title: "ثيم رائد"
description: "<p>This demo store allows you to explore the look and design of stores on the Salla platform. Browse the categories, try the shopping experience, and preview t…"
site: "@https://x.com/SallaApp"
images: "https://cdn.salla.network/salla.com/logo-wide-1.svg"
meta: []
links: []
styles: []
scripts: []
importMap: []
alternateLanguages: undefined
jsonLd: undefined
Controls
extension openGraph.images
extension meta
What a theme writes
import { buildBaseHead } from '@salla.sa/twilight-theme-engine/utils/baseHead';
import { mergeHead, type HeadDescriptor } from '@salla.sa/twilight-theme-engine/utils/head';
import type { StoreContext } from '@salla.sa/twilight-theme-engine/api/store';

export function saleHead(settings: StoreContext, locale: string): HeadDescriptor {
  return mergeHead(buildBaseHead(settings, locale), {
    title: 'Summer sale',
  });
}

Example

app/routes/cart.tsx
import { createFileRoute } from '@tanstack/react-router';
import { Cart, type CartPageProps } from '@salla.sa/twilight-theme-engine/routes/cart';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
import { mergeHead } from '@salla.sa/twilight-theme-engine/utils/head';

export const Route = createFileRoute('/{-$locale}/cart')({
  loader: ({ params }): Promise<CartPageProps> => Cart.loader({ locale: params.locale }),
  // The engine's cart head, plus two tags of the theme's own.
  head: withHead(Cart, (result) =>
    mergeHead(result, {
      robots: 'noindex, follow',
      meta: [{ name: 'referrer', content: 'strict-origin-when-cross-origin' }],
    })
  ),
  component: CartComponent,
});

function CartComponent() {
  return <Cart.Component {...Route.useLoaderData()} />;
}

How it behaves

  • Appended, base first: meta, links, styles, scripts and importMap. Nothing is de-duplicated. The five lists are always in the result, as empty arrays when neither side had any.

  • Merged one level deep ({ ...base.x, ...extension.x }), and only when either side has them: openGraph and twitter.

  • Taken from the extension when it is not null or undefined, else from the base: alternateLanguages and jsonLd.

  • Every other field (title, description, keywords, canonical, robots) comes from an object spread: the extension wins whenever it has the key.

  • The root route builds every page's base head as mergeHead(buildBaseHead(settings, locale), { meta, links, styles, importMap, scripts }).

Gotchas

  • A key that is present but undefined still wins: mergeHead(base, { title: undefined }) has no title. Leave the key out instead, for example with ...(title ? { title } : {}).

  • openGraph.images and twitter.images are replaced, not appended: adding { openGraph: { images: photo } } drops any image the base had, such as the store logo from buildBaseHead.

  • Merging the same extension twice duplicates its tags.

  • docs/07-data-types.md says openGraph and twitter are deep-merged, leaves out importMap, and imports mergeHead from the package root, which does not export it. The behaviour above is what the code does.

Related

Source and docs