Page Templates
A page template defines the structure of a page — the component groups where admins add and manage content. The PHP Page entity's uiComponent field maps to your Vue file. For dynamic pages (isTemplate: true), many routes share one template but each route points to its own PageData record.
File Convention
app/app/cwa/pages/Primary.vue → CwaPagePrimary
app/app/cwa/pages/BlogDetail.vue → CwaPageBlogDetail
The Page's uiComponent holds the full registered name, CwaPageBlogDetail. The API adds the CwaPage prefix if a page is saved with the short name, so the admin and the fixture builder store the same value. See How uiComponent Names Resolve.
Every Page Component Receives :iri
The module passes the page IRI as a prop. Declare it:
import type { IriProp } from '#cwa/composables/cwa-resource'
defineProps<IriProp>()
The iri prop is the IRI of the current Page entity. Pass it as the location to any CwaComponentGroup.
Minimal Page Template
<!-- app/app/cwa/pages/Primary.vue -->
<template>
<main>
<CwaComponentGroup reference="content" :location="iri" />
</main>
</template>
<script setup lang="ts">
import type { IriProp } from '#cwa/composables/cwa-resource'
defineProps<IriProp>()
</script>
Multiple Component Groups
A real page usually has several groups, such as hero, main body and sidebar:
<template>
<div>
<section class="hero">
<CwaComponentGroup reference="hero" :location="iri" />
</section>
<div class="grid lg:grid-cols-3 gap-8">
<div class="lg:col-span-2">
<CwaComponentGroup reference="main" :location="iri" />
</div>
<aside>
<CwaComponentGroup reference="sidebar" :location="iri" />
</aside>
</div>
</div>
</template>
<script setup lang="ts">
import type { IriProp } from '#cwa/composables/cwa-resource'
defineProps<IriProp>()
</script>
The style classes an admin selects for the page are applied to your page component's root element automatically — you don't bind them yourself.
Dynamic Pages: Accessing Page Data
When the page is a template (isTemplate: true), cwa.resources.pageData contains the current record's data:
const cwa = useCwa()
const pageData = computed(() => cwa.resources.pageData.value)
const headline = computed(() => pageData.value?.data?.headline)
const createdAt = computed(() => pageData.value?.data?.createdAt)
The module sets the <title> and <meta name="description"> from the page data and page for you, so you don't need useHead for the basics. See Per-Page SEO for how they are chosen and how to override them.
Registering Pages
// nuxt.config.ts
cwa: {
pages: {
Primary: {
name: 'Standard Page',
classes: {
'Full Width': 'full-width'
}
},
BlogDetail: {
name: 'Blog Article'
}
}
}
classes is a map of label → class string. A "Default" option is added automatically, and you omit classes entirely when a template has no style variants.
Loading Bar
Put <CwaLoadingIndicator /> in app/app/app.vue, above <NuxtLayout>:
<!-- app/app/app.vue -->
<template>
<CwaLoadingIndicator /> <NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
It wraps Nuxt's <NuxtLoadingIndicator> and passes its props through, such as color, height, throttle and duration. On a CWA page the bar shows how much of the page's content has loaded, and it never goes back when the load finds more resources, such as a route's page data. On your own Nuxt pages it shows Nuxt's usual timed estimate. One bar covers both.
The module holds Nuxt's page:loading:end hook until the new CWA page shows. So the bar finishes, and the page scrolls to the top, when the new page is on screen, not while the old one is still showing. Anything your app hooks on page:loading:end runs then too. Pages with definePageMeta({ cwa: { disabled: true } }) are never held.
<CwaUiProgressBar> as well. It's driven by pageLoadProgress and still works, but <CwaLoadingIndicator> replaces it. With both, two bars show.Page Load State
const isPageLoading = computed(() => cwa.resources.isLoading.value)
const progress = computed(() => cwa.resources.pageLoadProgress.value)
// { total: 8, complete: 5, percent: 63, resources: [...IRIs] }
// percent is rounded to a whole number
<CwaLoadingIndicator> already shows percent. Read it yourself only for your own loading UI.
Nested Pages
If your pages have a parent–child URL structure — events listing → event detail, blog → post, docs section → page — add <CwaPage /> wherever the child should render inside the parent template. It works like <NuxtPage /> but for CWA's depth-aware hierarchy.
See <CwaPage /> for complete working examples, the inject key reference, and configuration.