skills/nuxt/SKILL.md
Use when editing Nuxt apps, nuxt.config.ts, nuxt.config.js, .nuxt directories, useFetch, useAsyncData, Nitro server routes, SSR, SSG, or Vue server rendering with Nuxt.
npx skillsauth add cofin/flow 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.
<!-- pages/users/[id].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: user, error } = await useFetch(`/api/users/${route.params.id}`);
definePageMeta({
layout: 'admin',
middleware: ['auth'],
});
useHead({
title: () => user.value?.name ?? 'User',
});
</script>
<template>
<div v-if="error">Error: {{ error.message }}</div>
<div v-else-if="user">
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</template>
</example>
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
const user = await db.users.findUnique({ where: { id } });
if (!user) {
throw createError({
statusCode: 404,
message: 'User not found',
});
}
return user;
});
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const user = await db.users.create({ data: body });
return user;
});
</example>
// composables/useAuth.ts
export function useAuth() {
const user = useState<User | null>('auth-user', () => null);
const isAuthenticated = computed(() => !!user.value);
async function login(credentials: Credentials) {
const { data } = await useFetch('/api/auth/login', {
method: 'POST',
body: credentials,
});
user.value = data.value;
}
async function logout() {
await useFetch('/api/auth/logout', { method: 'POST' });
user.value = null;
navigateTo('/login');
}
return { user, isAuthenticated, login, logout };
}
</example>
<script setup lang="ts">
// Simple fetch
const { data, pending, error, refresh } = await useFetch('/api/items');
// With options
const { data: items } = await useFetch('/api/items', {
query: { page: 1, limit: 10 },
pick: ['id', 'name'], // Only include these fields
transform: (data) => data.items,
watch: [page], // Re-fetch when page changes
});
// Lazy fetch (doesn't block navigation)
const { data, pending } = useLazyFetch('/api/slow-data');
// useAsyncData for custom async operations
const { data } = await useAsyncData('key', () => {
return $fetch('/api/items');
});
</script>
</example>
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { isAuthenticated } = useAuth();
if (!isAuthenticated.value && to.path !== '/login') {
return navigateTo('/login');
}
});
// middleware/admin.ts (named middleware)
export default defineNuxtRouteMiddleware(() => {
const { user } = useAuth();
if (user.value?.role !== 'admin') {
throw createError({
statusCode: 403,
message: 'Forbidden',
});
}
});
</example>
// plugins/api.ts
export default defineNuxtPlugin(() => {
const api = $fetch.create({
baseURL: '/api',
onRequest({ options }) {
const token = useCookie('token');
if (token.value) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token.value}`,
};
}
},
});
return {
provide: { api },
};
});
// Usage: const { $api } = useNuxtApp();
</example>
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/blog/**': { isr: 3600 }, // ISR: revalidate every hour
'/admin/**': { ssr: false }, // SPA mode
'/api/**': { cors: true },
},
});
</example>
// With useState (SSR-safe)
const count = useState('counter', () => 0);
// With Pinia
// stores/user.ts
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null);
async function fetch() {
user.value = await $fetch('/api/user');
}
return { user, fetch };
});
</example>
useFetch for data fetching (handles SSR)useState for SSR-safe reactive statedefinePageMeta for page-level configuseFetch or useAsyncData for data fetching -- These composables are SSR-aware and prevent duplicate requests on the client. Never use plain $fetch in a component's top-level setup.import.meta.client or use onMounted before accessing window, document, or localStorage.server/ directory for sensitive operations -- Keep database queries, API keys, and complex logic in Nitro server routes to ensure they never leak to the client.useAsyncData -- This is critical for proper hydration and preventing data mismatch between server and client.useState over local refs for global state -- useState is SSR-safe and preserves state during hydration.
</guardrails>
useFetch or useAsyncData is used for all top-level data fetchingserver/api/ directoryuseAsyncData calls have unique and stable keysdefinePageMeta is used for route-level guards and layouts<ClientOnly> or used within onMounted
</validation>
testing
Use when syncing Beads state to markdown, checking Flow status, refreshing context docs, validating task markers, or reporting ready/blocked Flow work.
testing
Use when initializing Flow in a repo, configuring .agents, installing or checking Beads bd, setting local-only sync policy, or creating first project context files.
data-ai
Use when drafting PRDs, researching, planning, refining, revising, or creating .agents/specs/<flow_id>/spec.md worksheets for Flow.
testing
Use when implementing Flow tasks from Beads or spec.md, claiming ready work, applying TDD, recording task notes, committing, and syncing after task state changes.