.agents/skills/nuxt/SKILL.md
Use when working with Nuxt 4 concepts — routing, composables, data fetching, server routes, layouts, middleware, plugins, auto-imports, SSR/hydration, runtime config, state management, error handling, and testing. Load this skill before writing or modifying any Nuxt-specific code in this project.
npx skillsauth add antoinezanardi/goat-it-web-admin nuxtInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This project uses Nuxt 4 (^4.3.1) with the Nuxt 4 compatibility layer enabled.
Full Nuxt v4 documentation index: https://nuxt.com/llms.txt
Deep-dive any topic by fetching the corresponding raw doc URL listed there (e.g. https://nuxt.com/raw/docs/4.x/getting-started/data-fetching.md).
app/
assets/ # Processed by Vite (CSS, fonts, images)
components/ # Auto-imported Vue components (PascalCase)
composables/ # Auto-imported composables (use*.ts)
layouts/ # Layout components (default.vue, etc.)
middleware/ # Route middleware
pages/ # File-based routing
plugins/ # Vue plugins & Nuxt plugin hooks
utils/ # Auto-imported utility functions
app.vue # Root component
app.config.ts # Reactive runtime app configuration
error.vue # Full-screen error page
server/
api/ # API routes (*.get.ts, *.post.ts, …)
middleware/ # Server middleware
plugins/ # Nitro plugins
utils/ # Server-only utilities
shared/ # Shared between app and server
public/ # Static assets (not processed)
nuxt.config.ts # Nuxt configuration
app/pages/)Every .vue file in pages/ becomes a route. Dynamic segments use [param] syntax.
pages/
index.vue → /
about.vue → /about
users/
index.vue → /users
[id].vue → /users/:id
[...slug].vue → /users/* (catch-all)
Use <NuxtPage /> in app.vue or layouts to render the matched page.
Use <NuxtLink to="/about"> for client-side navigation.
Nuxt auto-imports:
ref, computed, watch, …)app/composables/ (use*.ts)app/utils/app/components/useRoute, useRouter, useFetch, useState, …)No explicit import statements needed for these in .vue files or composables.
app/layouts/)<!-- app/layouts/default.vue -->
<template>
<div>
<AppNav/>
<slot/>
</div>
</template>
Set a layout per page:
<!-- app/pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({ layout: 'dashboard' })
</script>
app/middleware/)Route guards that run before navigation. Named middleware files (auth.ts) are referenced in definePageMeta.
// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
const { loggedIn } = useAuth()
if (!loggedIn.value) return navigateTo('/login')
})
<script setup lang="ts">
definePageMeta({ middleware: 'auth' })
</script>
app/plugins/)Run code when the Vue app initialises. Return helpers via provide to make them available via useNuxtApp().
// app/plugins/my-plugin.ts
export default defineNuxtPlugin(() => {
return { provide: { greet: (name: string) => `Hello, ${name}!` } }
})
Prefer useFetch / useAsyncData for SSR-safe data fetching. Both deduplicate requests between server and client.
// Simple fetch
const {
data,
status,
error,
refresh
} = await useFetch('/api/users')
// With options
const { data } = await useFetch('/api/users', {
method: 'POST',
body: { name: 'Alice' },
pick: [
'id',
'name'
]
})
// Custom key + transform
const { data } = await useAsyncData('user-42', () => $fetch('/api/users/42'), {
transform: (u) => ({
id: u.id,
fullName: `${u.first} ${u.last}`
})
})
Use useLazyFetch / useLazyAsyncData to skip blocking navigation (data loads after mount).
For server-only calls (e.g. calling internal DB), use $fetch inside useAsyncData — $fetch on the server does not make an HTTP round-trip to the Nitro handler; it calls it directly.
Lightweight shared reactive state:
// app/composables/useCounter.ts
export const useCounter = () => useState('counter', () => 0)
For complex global state this project uses Pinia (use<Entity>Store naming convention).
Define server-only and public runtime config in nuxt.config.ts.
Values are overridden by env vars: NUXT_API_SECRET, NUXT_PUBLIC_API_BASE.
API routes live in server/api/. File name determines HTTP method.
// server/api/users.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
return fetchUsers(query)
})
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
return createUser(body)
})
Access from the client with useFetch('/api/users') or $fetch('/api/users').
// Throw a typed HTTP error from a server route
throw createError({
statusCode: 404,
statusMessage: 'Not found'
})
// Show the error page from a component/composable
throw createError({
statusCode: 500,
fatal: true
})
// Clear a handled error
clearError({ redirect: '/' })
Custom error page: app/error.vue receives an error prop.
// Per page
useHead({
title: 'My page',
meta: [
{
name: 'description',
content: '…'
}
]
})
useSeoMeta({
title: 'My page',
ogTitle: 'My page',
description: '…'
})
<ClientOnly> / hydrationWrap components that must only render on the client:
<ClientOnly>
<ChartComponent/>
<template #fallback><p>Loading chart…</p></template>
</ClientOnly>
Avoid reading browser-only APIs (window, localStorage) outside onMounted or <ClientOnly> to prevent hydration mismatches.
callOnceRun a function exactly once (server OR client, never both):
await callOnce(async () => {
await store.fetchInitialData()
})
| Composable | Purpose |
|---------------------------------|---------------------------------------------|
| useRoute() | Current route object |
| useRouter() | Router instance (push, replace, back) |
| useFetch(url, opts) | SSR-safe fetch with caching |
| useAsyncData(key, fn) | SSR-safe async data |
| useState(key, init) | SSR-friendly shared reactive state |
| useCookie(name, opts) | Read/write cookies (SSR-safe) |
| useRuntimeConfig() | Access runtime config |
| useNuxtApp() | Access app instance, plugins, hooks |
| useRequestHeaders(keys) | Access incoming request headers (server) |
| useError() | Current Nuxt error |
| useHead(opts) | Set <head> metadata |
| useSeoMeta(opts) | Flat SEO meta API |
| navigateTo(to) | Programmatic navigation |
| clearError(opts) | Clear the global error |
| createError(opts) | Create a typed H3 error |
| definePageMeta(meta) | Page-level metadata (layout, middleware, …) |
| defineNuxtRouteMiddleware(fn) | Declare route middleware |
| defineNuxtPlugin(fn) | Declare a Nuxt plugin |
This project uses @nuxt/test-utils with Vitest and happy-dom.
import { mountSuspended } from '@nuxt/test-utils/runtime'
import MyComponent from '~/components/MyComponent.vue'
it('renders correctly', async () => {
const wrapper = await mountSuspended(MyComponent, {
props: { title: 'Hello' }
})
expect(wrapper.text()).toContain('Hello')
})
Use shallow: true for layout/wrapper tests to avoid deep rendering of child components.
// nuxt.config.ts
export default defineNuxtConfig({
modules: [], // Nuxt modules
css: [], // Global CSS files
runtimeConfig: {}, // Server/public runtime config
app: {
head: {}, // Global <head> defaults
pageTransition: {}, // Page transition config
},
imports: { dirs: [] }, // Extra auto-import dirs
components: [], // Extra component dirs / options
typescript: {
strict: true,
typeCheck: true,
},
compatibilityDate: '', // Required in Nuxt 4
})
Fetch any section on demand from the raw doc URLs in https://nuxt.com/llms.txt.
Key v4 pages:
https://nuxt.com/raw/docs/4.x/getting-started/routing.mdhttps://nuxt.com/raw/docs/4.x/getting-started/data-fetching.mdhttps://nuxt.com/raw/docs/4.x/getting-started/server.mdhttps://nuxt.com/raw/docs/4.x/getting-started/state-management.mdhttps://nuxt.com/raw/docs/4.x/getting-started/error-handling.mdhttps://nuxt.com/raw/docs/4.x/guide/concepts/auto-imports.mdhttps://nuxt.com/raw/docs/4.x/guide/concepts/rendering.mdhttps://nuxt.com/raw/docs/4.x/guide/going-further/runtime-config.mdhttps://nuxt.com/raw/docs/4.x/guide/going-further/hooks.mdhttps://nuxt.com/raw/docs/4.x/getting-started/testing.mdhttps://nuxt.com/raw/docs/4.x/api.mddevelopment
Use when working with VueUse composables - track mouse position with useMouse, manage localStorage with useStorage, detect network status with useNetwork, debounce values with refDebounced, and access browser APIs reactively. Check VueUse before writing custom composables - most patterns already implemented.
testing
Use when writing or modifying unit tests in this project. Load this skill before creating any *.spec.ts file. Covers all five Vitest projects, mock patterns, faketories, composable/store/repository test wiring, and coverage requirements.
development
Build UIs with @nuxt/ui v4 — 125+ accessible Vue components with Tailwind CSS theming. Use when creating interfaces, customizing themes to match a brand, building forms, or composing layouts like dashboards, docs sites, and chat interfaces.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.