<CwaPage />
In standard Nuxt, <NuxtPage /> is how you tell the router where to mount the next matched route's page component. <CwaPage /> fills the same role in CWA: place it inside a page template to render whatever page or PageData record the router has resolved at the next depth level.
Unlike <NuxtPage />, you never manage route nesting in definePageMeta — the CWA API owns the hierarchy. <CwaPage /> reads from the manifest and renders the right template automatically.
When You Need It
Any time URLs have a parent–child relationship:
| Parent | Child | URL shape |
|---|---|---|
| Events listing | Individual event | /events → /events/2024-conference |
| Blog | Article | /blog → /blog/my-first-post |
| Docs section | Page | /docs/api → /docs/api/authentication |
| Product category | Product detail | /shop/shoes → /shop/shoes/white-runners |
Without <CwaPage /> in the parent template, navigating to a child URL loads the correct data but has nowhere to render it.
A Complete Example
Events listing with a detail page. The listing template has content regions of its own; the detail template reads custom fields from the event's PageData record.
Parent template
<!-- app/cwa/pages/EventsList.vue -->
<template>
<div>
<CwaComponentGroup reference="hero" :location="iri" />
<CwaComponentGroup reference="events-grid" :location="iri" />
<!--
Renders the child event page.
Empty when at /events; renders EventDetail at /events/2024-conference.
-->
<CwaPage />
</div>
</template>
<script setup lang="ts">
import type { IriProp } from '@cwa/nuxt/runtime/composables'
defineProps<IriProp>()
</script>
Child template
<!-- app/cwa/pages/EventDetail.vue -->
<template>
<article>
<header class="mb-8">
<h1 class="text-4xl font-bold">{{ title }}</h1>
<time v-if="date" class="text-neutral-500">{{ formattedDate }}</time>
</header>
<CwaComponentGroup reference="content" :location="iri" />
</article>
</template>
<script setup lang="ts">
import { inject, computed } from 'vue'
import type { ComputedRef } from 'vue'
import type { IriProp } from '@cwa/nuxt/runtime/composables'
defineProps<IriProp>()
const cwa = useCwa()
// Injected by <CwaPage /> — the IRI of the EventData record at this depth
const pageDataIri = inject<ComputedRef<string | undefined>>('cwa-page-data-iri')
const eventData = computed(() => {
if (!pageDataIri?.value) return null
return cwa.resources.getResource(pageDataIri.value).value?.data
})
const title = computed(() => eventData.value?.title ?? '')
const date = computed(() => eventData.value?.date as string | null ?? null)
const formattedDate = computed(() => {
if (!date.value) return ''
return new Intl.DateTimeFormat('en-GB', { dateStyle: 'long' }).format(new Date(date.value))
})
</script>
The pattern is the same at every depth: inject cwa-page-data-iri, read via getResource(), derive computed properties from .data.
Three Levels Deep
<CwaPage /> stacks — a mid-level template can include its own <CwaPage /> to render the next level down. A blog with listing → category → post looks like this:
Listing (depth 0) — shell with no PageData
<!-- app/cwa/pages/BlogListing.vue -->
<template>
<div>
<CwaComponentGroup reference="hero" :location="iri" />
<CwaPage />
</div>
</template>
<script setup lang="ts">
import type { IriProp } from '@cwa/nuxt/runtime/composables'
defineProps<IriProp>()
</script>
Category (depth 1) — reads its own PageData, renders child posts
<!-- app/cwa/pages/BlogCategory.vue -->
<template>
<section>
<h2 class="text-2xl font-semibold">{{ name }}</h2>
<p class="text-neutral-500">{{ description }}</p>
<CwaPage />
</section>
</template>
<script setup lang="ts">
import { inject, computed } from 'vue'
import type { ComputedRef } from 'vue'
import type { IriProp } from '@cwa/nuxt/runtime/composables'
defineProps<IriProp>()
const cwa = useCwa()
const pageDataIri = inject<ComputedRef<string | undefined>>('cwa-page-data-iri')
const data = computed(() => {
if (!pageDataIri?.value) return null
return cwa.resources.getResource(pageDataIri.value).value?.data
})
const name = computed(() => data.value?.name ?? '')
const description = computed(() => data.value?.description ?? '')
</script>
Post (depth 2) — leaf node, no <CwaPage />
<!-- app/cwa/pages/BlogPost.vue -->
<template>
<article>
<h1 class="text-4xl font-bold">{{ title }}</h1>
<CwaComponentGroup reference="body" :location="iri" />
</article>
</template>
<script setup lang="ts">
import { inject, computed } from 'vue'
import type { ComputedRef } from 'vue'
import type { IriProp } from '@cwa/nuxt/runtime/composables'
defineProps<IriProp>()
const cwa = useCwa()
const pageDataIri = inject<ComputedRef<string | undefined>>('cwa-page-data-iri')
const title = computed(() => {
if (!pageDataIri?.value) return ''
return cwa.resources.getResource(pageDataIri.value).value?.data?.title ?? ''
})
</script>
The inject key pattern is identical at every depth. <CwaPage /> always provides cwa-page-data-iri scoped to its own level, so BlogCategory and BlogPost both use the same inject without any depth arithmetic.
Auto-Fallback
If your page template doesn't include <CwaPage /> but the router has resolved a child page at the next depth level, CWA automatically appends one after your template renders. No child page is silently dropped.
This kicks in when depthCount > currentDepth + 1 and no explicit <CwaPage /> was registered inside the template. The injected instance uses :auto-fallback="true" internally, which prevents it from self-registering as a child (which would cause a loop).
The practical effect: intermediate templates that don't care about positioning the child can omit <CwaPage /> entirely and the child will still render — appended after the template's own output. For control over where the child appears, include <CwaPage /> explicitly.
What <CwaPage /> Provides
When <CwaPage /> renders a child template, it injects three keys available to that template and all descendants:
| Key | Type | Value |
|---|---|---|
cwa-page-data-iri | ComputedRef<string | undefined> | IRI of the PageData record at this depth, if any |
cwa-page-own-depth | number | The depth level this template is rendering at |
cwa-page-depth | number | The depth for a nested <CwaPage /> inside this template |
cwa-page-data-iri is undefined when the depth has a static sub-page with no PageData (e.g. /about/team as a Page child of /about).
Configuration
// nuxt.config.ts
cwa: {
pagesDepth: 3, // supports depth 0, 1, 2 (three URL segments)
pages: {
BlogListing: { name: 'Blog Listing' },
BlogCategory: { name: 'Blog Category' },
BlogPost: { name: 'Blog Post' }
}
}
pagesDepth controls how many levels the module pre-fetches from the manifest. Three URL segments (/blog/tech/my-post) means pagesDepth: 3.
How Navigation Works
Navigating between child pages re-renders only the <CwaPage /> subtree. The parent layout, page shell, and component groups outside <CwaPage /> stay mounted, making in-section navigation feel instant even for server-rendered pages.
For the API side of nested pages — setting parentPage / parentPageData in PHP, route auto-prefixing, cascading renames, and the resource manifest format — see Dynamic & Nested Pages.