mergeHead
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
title: "Summer sale" · openGraph.title: "ثيم رائد" · openGraph.images: "https://cdn.salla.network/salla.com/logo-wide-1.svg" · meta: 0
mergeHead(base, extension): {…} 12 keys
openGraph: {…} 7 keys
twitter: {…} 5 keys
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
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,scriptsandimportMap. 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:openGraphandtwitter.Taken from the extension when it is not
nullorundefined, else from the base:alternateLanguagesandjsonLd.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
undefinedstill wins:mergeHead(base, { title: undefined })has no title. Leave the key out instead, for example with...(title ? { title } : {}).openGraph.imagesandtwitter.imagesare replaced, not appended: adding{ openGraph: { images: photo } }drops any image the base had, such as the store logo frombuildBaseHead.Merging the same extension twice duplicates its tags.
docs/07-data-types.md says
openGraphandtwitterare deep-merged, leaves outimportMap, and importsmergeHeadfrom the package root, which does not export it. The behaviour above is what the code does.
Related
The plain object a route's head() returns: title, description, Open Graph and Twitter cards, hreflang links, JSON-LD and extra tags.
buildBaseHead, localeToOgLocaleBuilds the store-wide head defaults (title, description, canonical, Open Graph, Twitter card) from the store settings; the root route already applies them.
CartThe cart page, whose loader returns only a title because the visitor's cart id exists in the browser, not on the server.