Frontend-core and orchestr expose Nuxt runtime hooks that let you extend or modify the complete behaviour of your Laioutr Frontend. Register hooks inside a Nuxt plugin for client-side hooks, or a Nitro plugin for server-side hooks.
These hooks run on the client. Register them in a Nuxt plugin with nuxtApp.hook().
Three hooks let you override how linkResolver resolves links, switches locale paths, and switches market URLs. If your hook sets result.value, the default resolution is skipped entirely.
| Hook | Arguments | When it fires |
|---|---|---|
frontend-core:link-resolver:resolve | { link: Link, result } | On every call to linkResolver.resolve() |
frontend-core:link-resolver:switch-locale-path | { targetLanguageId: string, result } | When switching the current page to another language |
frontend-core:link-resolver:switch-market-url | { targetMarketId: string, targetLanguageId?: string, result } | When switching to a different market (may include host change) |
In all three cases, result has the shape { value: string | undefined } and can be used to override the default resolution.
export default defineNuxtPlugin((nuxtApp) => {
// Resolve product references to an external catalog URL
nuxtApp.hook('frontend-core:link-resolver:resolve', ({ link, result }) => {
if (link.type === 'reference' && link.reference.type === 'Product') {
result.value = `https://catalog.example.com/p/${link.reference.slug}`;
}
});
// Override locale switching for a specific market
nuxtApp.hook('frontend-core:link-resolver:switch-locale-path', ({ targetLanguageId, result }) => {
if (targetLanguageId === 'fr-CH') {
result.value = `/fr-ch${useRoute().path}`;
}
});
});
This hook lets you control which page variant is rendered. Pages can have multiple variants (for A/B testing, personalization, or conditional layouts). If you set result.value to a RenderPageVariant, that variant is used instead of the default.
| Hook | Arguments | When it fires |
|---|---|---|
frontend-core:page-renderer:select-page-variant | { page: RenderPage, result } | When the PageRenderer component selects a variant |
Here result has the shape { value: RenderPageVariant | undefined }. The page object contains id, type, path, and a variants array.
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('frontend-core:page-renderer:select-page-variant', ({ page, result }) => {
const variantId = useCookie('ab-variant').value;
const match = page.variants.find((v) => v.id === variantId);
if (match) {
result.value = match;
}
});
});
These hooks fire during client-side action execution. They follow the lifecycle pattern: before fires before the request, success or error after resolution, and finally always. All receive a token string that identifies the action (e.g. ecommerce/cart/add-items).
Fired by fetchAction, useFetchAction, useQueryAction, and useMutationAction (which uses fetchAction internally).
| Hook | Arguments |
|---|---|
orchestr:action:fetch:before | { token, input } |
orchestr:action:fetch:success | { token, output } |
orchestr:action:fetch:error | { token, error } |
orchestr:action:fetch:finally | { token, output?, error?, input } |
Fired by useMutationAction.
| Hook | Arguments |
|---|---|
orchestr:action:mutation:before | { token, input } |
orchestr:action:mutation:success | { token, output, input, context } |
orchestr:action:mutation:error | { token, error, context } |
orchestr:action:mutation:finally | { token, output?, error?, input, context } |
The context value comes from Pinia Colada's mutation context and is set by the onMutate callback.
export default defineNuxtPlugin((nuxtApp) => {
// Track all failed actions (both fetch and mutation)
nuxtApp.hook('orchestr:action:fetch:error', ({ token, error }) => {
errorTracker.capture(error, { action: token, type: 'fetch' });
});
nuxtApp.hook('orchestr:action:mutation:error', ({ token, error }) => {
errorTracker.capture(error, { action: token, type: 'mutation' });
});
});
The orchestr:client-env:modify hook fires synchronously every time orchestr builds the clientEnv object before sending an action request. Use it to set locale, currency, or custom fields based on the current frontend state.
| Hook | Arguments |
|---|---|
orchestr:client-env:modify | { clientEnv: ClientEnv } |
The ClientEnv object has the following shape:
{
locale: string;
currency: string;
isPreview?: boolean;
custom?: Record<string, any>;
}
Mutate clientEnv directly; don't replace it.
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('orchestr:client-env:modify', ({ clientEnv }) => {
clientEnv.locale = useLanguage().value.locale;
clientEnv.currency = useCurrency().value;
});
});
async). Avoid async work inside the callback.These hooks fire during server-side action handler execution. They are Nitro runtime hooks and must be registered in a Nitro plugin, not a Nuxt plugin.
| Hook | Arguments |
|---|---|
orchestr:action:handler:before | { token, input, clientEnv } |
orchestr:action:handler:error | { token, error } |
orchestr:action:handler:success | { token, output } |
orchestr:action:handler:finally | { token, output?, error?, input } |
export default defineNitroPlugin((nitroApp) => {
const pending = new Map<string, number>();
nitroApp.hooks.hook('orchestr:action:handler:before', ({ token }) => {
pending.set(token, Date.now());
});
nitroApp.hooks.hook('orchestr:action:handler:error', ({ token, error }) => {
const startedAt = pending.get(token);
const duration = startedAt ? Date.now() - startedAt : undefined;
console.error(`[orchestr] ${token} failed after ${duration}ms`, error);
pending.delete(token);
});
});
Environments & Staging
How to preview changes before going live, using Vercel preview deployments and workarounds for environment-specific content.
Media and Media Library
Laioutr’s media library abstraction lets business users choose assets from connected backends visually in Cockpit. Implement your own media adapter for your asset system so editors can browse and select (and optionally upload) media in Studio.