Every page in Laioutr has a page type: a named kind of page that defines its URL shape, what data it loads, and how it appears in Studio. Customers add pages of a given type in Studio; developers define page types with definePageTypeToken:
import { definePageTypeToken } from '@laioutr-core/core-types/frontend'
import { ProductBySlugQuery } from './ProductBySlug.query'
export const ProductDetailPage = definePageTypeToken('ecommerce/product-detail-page', {
kind: 'dynamic',
studio: {
label: 'Product Detail Page',
group: 'Shop',
icon: 'tag',
},
requiredQueries: [
{
alias: 'product',
entityType: 'Product',
default: {
label: 'Page Product',
urlAlias: '_',
token: ProductBySlugQuery,
inputRules: { slug: { join: ['/', { var: 'route.params.slug' }] } },
},
},
],
pathConstraints: {
requiredParams: ['slug'],
default: '/products/:slug+',
},
resolveFor: [{ referenceType: 'Product' }],
})
The token is a branded string (e.g. 'ecommerce/product-detail-page') that carries its metadata at the type level. Registration happens at import time when definePageTypeToken runs.
kind: static vs dynamicstatic: the customer can create multiple pages of this type (e.g. Landing Page, Content Page). Each page has its own ID and path.dynamic: typically one page per project, driven by a route with params (e.g. Home at /, Product Detail at /products/:slug).pathConstraintsControls the URL shape for this page type:
exact: a fixed path (e.g. '/' for Home).default: default path pattern that Studio suggests when adding a new page (e.g. /products/:slug+).requiredParams: route param names (e.g. ['slug']) so Studio generates paths that include them.If you omit these, the platform derives a path from the page type name and required params.
requiredQueriesDeclares data the page needs from the current route:
alias: name used in page templates to reference the query result (e.g. 'product').entityType: the entity type for Studio and Orchestr (e.g. 'Product').default: a query template with a token (the query to execute) and inputRules that bind route params to query input. For example, { slug: { join: ['/', { var: 'route.params.slug' }] } } passes the route's :slug param to the product query.resolveForTells the link resolver which entity reference links should resolve to this page type. For example, Product Detail Page declares resolveFor: [{ referenceType: 'Product' }], so a link of type reference pointing to a Product entity resolves to this page's route with the product's slug.
studioControls how the page type appears in Studio's "Add page" UI:
label: display name (e.g. "Product Detail Page").group: groups pages in the page list (e.g. "Shop", "General").icon / variantIcon: icons for the page type and its variants.groupBy: optional grouping key (e.g. 'year' for landing pages).description: optional description shown in Studio.Create a file that calls definePageTypeToken. The token registers itself globally at import time.
// runtime/shared/pageTypes/recipe-detail.pagetype.ts
import { definePageTypeToken } from '@laioutr-core/core-types/frontend'
import { RecipeBySlugQuery } from '../queries/RecipeBySlug.query'
export const RecipeDetailPage = definePageTypeToken('my-app/recipe-detail', {
kind: 'dynamic',
studio: {
label: 'Recipe Detail',
group: 'Content',
icon: 'utensils',
},
requiredQueries: [
{
alias: 'recipe',
entityType: 'Recipe',
default: {
label: 'Page Recipe',
urlAlias: '_',
token: RecipeBySlugQuery,
inputRules: { slug: { join: ['/', { var: 'route.params.slug' }] } },
},
},
],
pathConstraints: {
requiredParams: ['slug'],
default: '/recipes/:slug',
},
resolveFor: [{ referenceType: 'Recipe' }],
})
For a static page type (e.g. a custom landing page kind), you can omit requiredQueries and resolveFor:
export const PromotionPage = definePageTypeToken('my-app/promotion', {
kind: 'static',
studio: {
label: 'Promotion Page',
group: 'Marketing',
icon: 'megaphone',
},
})
Registration happens when definePageTypeToken executes, so the module must be imported before the link resolver or Studio needs it. A Nuxt plugin that imports the tokens and references them is the standard pattern — this prevents the bundler from tree-shaking the imports away:
// plugins/pagetypes.ts
import { defineNuxtPlugin } from '#app'
import { pageTypeTokenRegistry } from '@laioutr-core/core-types/frontend'
import { RecipeDetailPage } from '../shared/pageTypes/recipe-detail.pagetype'
import { PromotionPage } from '../shared/pageTypes/promotion.pagetype'
export default defineNuxtPlugin(() => {
[RecipeDetailPage, PromotionPage].forEach((token) => pageTypeTokenRegistry.getMetadata(token))
})
Once the token is registered, Studio picks it up automatically. When a customer adds a new page of your type, Studio uses pathConstraints to suggest a default path and shows the page under the configured studio.group.
Routes are generated from RC pages at build time. Each RC page has a type string that matches the token name (e.g. 'my-app/recipe-detail'). The link resolver uses the page type's resolveFor metadata to map reference links to the correct route.
Multi-market
Laioutr's multi-market support lets you serve different regions (markets) from one project, each with its own domains, languages, and currency, configured in Cockpit.
PWA
Turn your Laioutr frontend into a Progressive Web App—installable on devices, with offline support and an app-like experience—using zero-config defaults and optional customization.