How hook slots render
What happens between hookRegistry.register and the content appearing: priority order, the merged context, subscriptions and apps.
A hook slot is a named, empty place in a page. Your theme registers functions for a name, and whatever they return appears in that place, without copying the page.
This page follows what happens between hookRegistry.register(…) and the content showing up. For the task itself, see the recipe Add content to every page with a hook slot.
1. The engine registers its own handlers
As soon as the hooks module loads, the engine puts a few handlers of its own in the registry: Google Tag Manager, a "store closed" notice and others. Each decides when the page renders whether to show anything.
In engine terms
hooks/index.ts imports registerDefaultHooks.tsx, which registers on body:start (GTM 100, Sift 90, store closed 80), body:end (bundles 100, fast checkout 90), cart:submit.start (price quote) and cart:items.start (pre-order notice). Importing @salla.sa/twilight-theme-engine/hooks is enough: calling registerDefaultHooks() again adds a second copy of each.
Priority, appending and re-rendering, live
Register a few handlers with different priorities. Each registration notifies the slot, which re-renders on its own, and the list stays sorted by priority.
import { hookRegistry, HookName } from '@salla.sa/twilight-theme-engine/hooks';
// app/hooks/index.tsx: register once, when the theme starts.
hookRegistry.register(
HookName.HEADER_START,
() => <p className="announcement">{'Free delivery over 200 SAR'}</p>,
);
import { hookRegistry, HookName, type HookContext } from '@salla.sa/twilight-theme-engine/hooks';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
interface DescriptionContext extends HookContext {
product?: Product;
}
// app/hooks/index.tsx: run once, when the theme starts.
hookRegistry.register(
HookName.PRODUCT_DESCRIPTION_END,
({ product, twilight }: DescriptionContext) => {
// The engine product page renders this slot twice; only one copy passes the product.
if (!product) return null;
return (
<p className="delivery-note">
{twilight.locale === 'ar' ? 'يصل خلال يومين' : 'Arrives in 2 days'}
</p>
);
},
80 // before handlers at the default priority, 50
);
Why it matters
- Content appears twice:
registerran twice (the registration file runs from two places, or it sits in a component body). On the engine product page, theproduct:single.description,.startand.endslots are rendered twice, once withproductin the context and once without. - Content is missing from the page source but appears a moment later: it was registered with
useHookor in an effect. Register at module scope for server HTML. - Other content disappears when a component unmounts: that component used
useHook, whose cleanup clears every handler on the name. - Nothing shows for a `HookName`: the engine may never render that slot (
search:*,homepage:*,body:inner,head:inner). Render<HookSlot name="…" />yourself. - A marketplace app does not show in a slot of your own: declare the name with
registerThemeHookSlots.
Go deeper: HookSlot, hookRegistry, useHook, HookName, the default hooks and registerThemeHookSlots.