| name | frontend |
| description | Build and modify the App Template frontend (Nuxt 4 SSR with PrimeVue, Tailwind, Pinia). Use when the user asks to create pages, components, stores, composables, or modify the frontend UI. Covers project-specific patterns, conventions, and anti-patterns. |
App Template Frontend Development
This skill covers building and modifying the App Template frontend ā a Nuxt 4 SSR app at frontend/.
Tech Stack
- Nuxt ^4.0.0 ā SSR enabled
- Vue ^3.5.0 ā Composition API (
<script setup>)
- PrimeVue ^4.0.0 ā UI components (explicit imports, no Nuxt module)
- Tailwind CSS 3 ā utility classes via PostCSS (no
@nuxtjs/tailwindcss)
- Pinia ā state management (
@pinia/nuxt module)
Critical Rules
1. PrimeVue: Always Import Explicitly
There is NO @primevue/nuxt-module. Components must be imported per-file:
<script setup>
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import Button from 'primevue/button'
import Tag from 'primevue/tag'
</script>
Plugin setup is in app/plugins/primevue.ts (Aura theme, dark mode, ToastService).
2. Nuxt 4 Routing: No file + directory with same name
In Nuxt 4, having both pages/foo.vue and pages/foo/ directory makes foo.vue a nested layout wrapper. This breaks direct navigation to /foo.
# WRONG: foo.vue becomes a layout wrapper, /foo may not render
pages/foo.vue
pages/foo/bar.vue
# CORRECT: use index.vue inside the directory
pages/foo/index.vue ā /foo
pages/foo/bar.vue ā /foo/bar
3. SSR Hydration: Use <ClientOnly> for dynamic UI
Wrap dynamic content in <ClientOnly> to avoid hydration mismatches:
<ClientOnly>
<DynamicComponent />
<template #fallback>
<div class="h-40 bg-surface-800 animate-pulse rounded-xl" />
</template>
</ClientOnly>
4. API Calls: Use native fetch() in stores
const res = await fetch('/api/v1/app/some-endpoint')
const data = await res.json()
5. Tailwind: PostCSS Config Only
Tailwind is configured via PostCSS in nuxt.config.ts:
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Project Layout
frontend/app/
āāā pages/ # Routes (dashboard, etc.)
āāā components/ # Vue components organized by domain
āāā stores/ # Pinia stores
ā āāā appConfig.ts # App name and config
āāā composables/ # Hooks
ā āāā useApi.ts # HTTP client (fetch wrapper)
ā āāā useAppConfig.ts # App name and config
ā āāā useRealtimeUpdates.ts # WebSocket composable
āāā plugins/ # primevue.ts, appConfig.ts
āāā layouts/ # default.vue (header + nav), public.vue
āāā types/ # api.ts (TypeScript interfaces)
āāā assets/css/ # styles.css (Tailwind layers + custom classes)
Frontend Proxy
The Nuxt server proxy (frontend/server/middleware/proxy.ts) forwards API requests to the backend.
When adding a new backend router, add its prefix to PROXY_PATHS:
const PROXY_PATHS = ['/api/', '/mcp/']
Creating New Pages
- Add
frontend/app/pages/my-page.vue
- Route is auto-registered as
/my-page
- Create store in
stores/ if page needs state
- Import PrimeVue components explicitly
Creating New Components
Components in frontend/app/components/ auto-import by directory/filename:
PageHeader.vue ā <PageHeader>
example/ExampleCard.vue ā <ExampleCard>
Organize by domain subdirectory.
Dev Server
make up
make fe-logs
make fe-run
Dev URL: http://app.localhost (via Traefik proxy)