The CWA is in heavy development
The CWA is still in alpha and not ready for production - some code and implementations are likely to change. If you would like to try out the CWA, please enjoy what we have provided and feel free to provide feedback, or get involved on GitHub.
DraftNuxt Module

Module Setup

Installing the CWA Nuxt module, configuring peer dependencies, and wiring up the nuxt.config options.

@cwa/nuxt is the front-end half of a CWA application. It connects your Nuxt application to a Symfony API powered by API Platform, providing automatic route resolution, a reactive resource store, real-time updates via Mercure, authentication pages, a full admin CMS panel, and composables for every content pattern.

You own your Vue templates — CWA drives what data they receive and in what order they render.

Installation

pnpm add @cwa/nuxt

The module requires these peer modules — add them all to your nuxt.config.ts:

// nuxt.config.ts
export default defineNuxtConfig({
    modules: [
        '@nuxt/ui',
        '@nuxt/image',
        '@nuxtjs/seo',
        '@pinia/nuxt',
        '@pinia-plugin-persistedstate/nuxt',
        'nuxt-security',
        '@cwa/nuxt'   // must come after its peers
    ]
})

Minimum Configuration

Set your API URL via runtimeConfig. The browser URL is used client-side; the server URL is used during SSR (can be an internal Docker network address):

export default defineNuxtConfig({
    runtimeConfig: {
        public: {
            cwa: {
                apiUrl: 'http://api-internal',             // server-side (SSR)
                apiUrlBrowser: 'https://api.example.com'  // client-side
            }
        }
    }
})

In a Docker Compose setup these are typically different — the internal hostname resolves only within the Docker network.

Full cwa: Config Reference

See Nuxt Config for the complete reference. The key options at a glance:

export default defineNuxtConfig({
    cwa: {
        resources: { /* your CMS component types */ },
        layouts:   { /* your layout component types */ },
        pages:     { /* your page template component types */ },
        pageData:  { /* your PageData resource classes */ },
        pagesDepth: 2,  // nested page depth (default: 4)
        siteConfig: { siteName: 'My App' }
    }
})

What the Module Auto-Provides

You do not create these — the module ships them:

Pages (override by creating the same path in app/pages/):

  • /login — email + password login form
  • /forgot-password — request a password reset email
  • /reset-password/[username]/[token] — set a new password
  • /verify-email/[username]/[token] — verify email on registration
  • /confirm-new-email/[username]/[newEmail]/[token] — confirm email change
  • /_cwa/* — the full admin panel

Middleware:

  • Route resolution on every navigation — fetches the manifest and resolves the layout, page, and page data IRIs from the current URL
  • Auth state hydration from the JWT cookie payload

Pinia stores (accessed via useCwa()):

  • resources — all fetched resource data, keyed by IRI
  • auth — authentication state and methods
  • forms — form view state and submission helpers
  • ui — admin panel open/closed state, selected component, etc.
  • siteConfig — site-wide settings fetched from the API

The useCwa() Composable

useCwa() (or $cwa in templates) is your entry point to everything the module manages:

const cwa = useCwa()

cwa.resources.layout.value         // current layout resource
cwa.resources.page.value           // current page resource
cwa.resources.pageData.value       // current page data (dynamic pages)
cwa.auth.isSignedIn.value          // boolean
cwa.auth.isAdmin.value             // boolean (ROLE_ADMIN+)
cwa.auth.user.value                // current user object
cwa.siteConfig.siteName.value      // the site name from API settings
cwa.ui.isEditing.value             // admin edit mode active

Mixing Your Own Pages with CWA {#mixing-your-own-pages-with-cwa}

CWA's catch-all route covers / and all paths. But you can still create regular Nuxt pages alongside it — your app/pages/ files take precedence over CWA's dynamic routing.

For pages you write yourself, use definePageMeta to control how much of CWA runs on that page:

Disable CWA route fetching

If your page has nothing to do with CWA content, disable the middleware entirely:

<!-- app/pages/status.vue -->
<script setup lang="ts">
definePageMeta({
    cwa: { disabled: true }
})
</script>

CWA will not fetch a route manifest or resolve any resources. Auth state and useCwa() are still available.

Use a static Nuxt layout

If you want to use one of your own Nuxt layouts instead of CWA's resolved layout:

<script setup lang="ts">
definePageMeta({
    cwa: { staticLayout: 'my-nuxt-layout' }
})
</script>

Use the CWA root layout on your own page

You can opt into cwa-root-layout to get the admin header and edit-mode overlay on a hand-coded page:

<script setup lang="ts">
definePageMeta({
    layout: 'cwa-root-layout',
    cwa: { disabled: true }   // disable CWA content fetching but keep the layout
})
</script>

This is useful for pages like account settings or checkout flows that are not CMS-managed but should still appear inside the site shell and support admin edit mode for other parts of the page.

TypeScript Support

The module exports types for all component patterns:

import type { IriProp } from '@cwa/nuxt/runtime/composables'

// Every CWA component receives :iri as a prop
defineProps<IriProp>()

API resource data is dynamically shaped (resource.data is typed as Record<string, any> or similar). Under strict mode TypeScript may flag index access on these types as potentially undefined, even in contexts where you've checked for it. If you encounter these conflicts and don't want to add ? or type assertions throughout your component code, set strict: false in tsconfig.json. Alternatively, keep strict: true and use optional chaining (resource.data?.title) consistently.