frontend/nuxtjs-project-starter/SKILL.md
Scaffold a Nuxt 3.x project with auto-imports, layouts, `definePageMeta`, `useFetch`/`useAsyncData`, server routes, Nitro config, and SSR/SPA mode conventions.
npx skillsauth add achreftlili/deep-dev-skills nuxtjs-project-starterInstall 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.
Scaffold a Nuxt 3.x project with auto-imports, layouts,
definePageMeta,useFetch/useAsyncData, server routes, Nitro config, and SSR/SPA mode conventions.
npx nuxi@latest init my-app
cd my-app
npm install
npx nuxi module add eslint
npm install tailwindcss @tailwindcss/vite
├── app/
│ ├── app.vue # Root app component
│ ├── pages/
│ │ ├── index.vue # / route (auto-routed)
│ │ ├── login.vue # /login
│ │ ├── dashboard.vue # /dashboard
│ │ └── users/
│ │ ├── index.vue # /users
│ │ └── [id].vue # /users/:id (dynamic route)
│ ├── layouts/
│ │ ├── default.vue # Default layout (wraps all pages)
│ │ └── auth.vue # Auth layout (login/register)
│ ├── components/
│ │ ├── ui/ # Reusable UI components (auto-imported)
│ │ └── layout/ # Header, Footer, Sidebar
│ ├── composables/ # Auto-imported composables (use* functions)
│ │ ├── useAuth.ts
│ │ └── useApi.ts
│ ├── middleware/ # Route middleware
│ │ └── auth.ts
│ ├── plugins/ # Nuxt plugins (run on app init)
│ │ └── init.ts
│ ├── assets/
│ │ └── css/
│ │ └── main.css # Tailwind CSS entry
│ └── error.vue # Global error page
├── server/
│ ├── api/
│ │ ├── health.get.ts # GET /api/health
│ │ └── users/
│ │ ├── index.get.ts # GET /api/users
│ │ ├── index.post.ts # POST /api/users
│ │ └── [id].get.ts # GET /api/users/:id
│ ├── middleware/ # Server middleware (runs on every request)
│ │ └── log.ts
│ └── utils/ # Server-only utilities
│ └── db.ts
├── public/ # Static files served at /
├── stores/ # Pinia stores (if using @pinia/nuxt)
│ └── auth.ts
├── types/ # Shared TypeScript types
│ └── index.ts
├── nuxt.config.ts # Nuxt configuration
├── tsconfig.json
└── .env.example # Required env vars template
ref, computed, watch), composables in composables/, components in components/, and utilities in utils/ are all auto-imported. No manual imports needed.pages/ automatically become routes. [param].vue for dynamic segments, [...slug].vue for catch-all.definePageMeta(): set page metadata (layout, middleware, title) directly in page components.layouts/. Default layout applies unless overridden via definePageMeta.server/api/ become API endpoints. Use method suffixes (.get.ts, .post.ts) or handle methods in a single file.ssr: false in config for SPA mode, or per-route with routeRules.nuxt.config.ts)export default defineNuxtConfig({
compatibilityDate: "2025-01-01",
// Enable/disable SSR globally
ssr: true,
// Nuxt modules
modules: [
"@nuxt/eslint",
"@pinia/nuxt",
],
// Vite plugins (Tailwind CSS v4)
vite: {
plugins: [
import("@tailwindcss/vite").then((m) => m.default()),
],
},
// Global CSS
css: ["~/assets/css/main.css"],
// Runtime config (env vars)
runtimeConfig: {
// Server-only (not exposed to client)
dbUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
// Public (exposed to client)
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || "/api",
},
},
// Per-route rendering rules
routeRules: {
"/": { prerender: true }, // SSG: prerender at build
"/dashboard/**": { ssr: true }, // SSR: server-render on request
"/admin/**": { ssr: false }, // SPA: client-only
"/api/**": { cors: true }, // CORS for API routes
"/blog/**": { isr: 3600 }, // ISR: revalidate every hour
},
// TypeScript strict mode
typescript: {
strict: true,
},
devtools: { enabled: true },
});
app/assets/css/main.css)@import "tailwindcss";
app/app.vue)<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
app/layouts/default.vue)<script setup lang="ts">
const route = useRoute();
</script>
<template>
<div class="min-h-screen bg-gray-50">
<header class="border-b bg-white px-6 py-4">
<nav class="flex items-center gap-6">
<NuxtLink to="/" class="text-lg font-bold">My App</NuxtLink>
<NuxtLink to="/dashboard" class="hover:text-blue-600">Dashboard</NuxtLink>
<NuxtLink to="/users" class="hover:text-blue-600">Users</NuxtLink>
</nav>
</header>
<main class="p-6">
<slot />
</main>
</div>
</template>
app/layouts/auth.vue)<template>
<div class="flex min-h-screen items-center justify-center bg-gray-100">
<div class="w-full max-w-md rounded-lg bg-white p-8 shadow">
<slot />
</div>
</div>
</template>
definePageMeta and Data Fetching<!-- app/pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
layout: "default",
middleware: "auth",
});
const { data: stats, status } = await useFetch("/api/dashboard/stats");
</script>
<template>
<div>
<h1 class="mb-4 text-2xl font-bold">Dashboard</h1>
<div v-if="status === 'pending'" class="animate-pulse">Loading stats...</div>
<div v-else-if="status === 'error'" class="text-red-600">Failed to load stats.</div>
<div v-else class="grid grid-cols-3 gap-4">
<div class="rounded border p-4">
<p class="text-sm text-gray-500">Users</p>
<p class="text-2xl font-bold">{{ stats?.userCount }}</p>
</div>
<div class="rounded border p-4">
<p class="text-sm text-gray-500">Revenue</p>
<p class="text-2xl font-bold">{{ stats?.revenue }}</p>
</div>
</div>
</div>
</template>
useFetch vs useAsyncData<script setup lang="ts">
// useFetch — shorthand for useAsyncData + $fetch
// Automatically dedupes, caches, and handles SSR hydration
const { data: users, refresh } = await useFetch("/api/users");
// useAsyncData — when you need custom fetching logic
const { data: profile } = await useAsyncData("user-profile", () => {
return $fetch(`/api/users/${route.params.id}`);
});
// Reactive params — refetches when route.params.id changes
const route = useRoute();
const { data: user } = await useFetch(() => `/api/users/${route.params.id}`);
// Lazy fetching — doesn't block navigation
const { data: notifications, status } = useLazyFetch("/api/notifications");
</script>
<!-- app/pages/users/[id].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: user, error } = await useFetch(`/api/users/${route.params.id}`);
if (error.value) {
throw createError({
statusCode: 404,
statusMessage: "User not found",
});
}
</script>
<template>
<div v-if="user">
<h1 class="mb-2 text-2xl font-bold">{{ user.name }}</h1>
<p class="text-gray-500">{{ user.email }}</p>
</div>
</template>
// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
const authStore = useAuthStore();
if (!authStore.isAuthenticated) {
return navigateTo("/login");
}
});
// server/api/users/index.get.ts
export default defineEventHandler(async () => {
// Access runtime config
const config = useRuntimeConfig();
// Fetch from DB or external API
const users = await $fetch(`${config.dbUrl}/users`);
return users;
});
// server/api/users/index.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
if (!body.name || !body.email) {
throw createError({
statusCode: 400,
statusMessage: "Name and email are required",
});
}
// Create user in DB
const user = { id: crypto.randomUUID(), ...body };
return user;
});
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id");
// Fetch user by ID
const user = await findUserById(id);
if (!user) {
throw createError({
statusCode: 404,
statusMessage: `User ${id} not found`,
});
}
return user;
});
// app/composables/useAuth.ts
export function useAuth() {
const user = useState<User | null>("auth-user", () => null);
const isAuthenticated = computed(() => user.value !== null);
async function login(email: string, password: string) {
const response = await $fetch("/api/auth/login", {
method: "POST",
body: { email, password },
});
user.value = response.user;
}
function logout() {
user.value = null;
navigateTo("/login");
}
return { user, isAuthenticated, login, logout };
}
// app/plugins/init.ts
export default defineNuxtPlugin(async () => {
// Runs once on app initialization (both server and client)
const { user } = useAuth();
// Attempt to restore session
try {
const session = await $fetch("/api/auth/session");
user.value = session.user;
} catch {
// No active session
}
});
<!-- app/error.vue -->
<script setup lang="ts">
import type { NuxtError } from "#app";
const props = defineProps<{
error: NuxtError;
}>();
function handleClearError() {
clearError({ redirect: "/" });
}
</script>
<template>
<div class="flex min-h-screen flex-col items-center justify-center gap-4">
<h1 class="text-4xl font-bold">{{ error.statusCode }}</h1>
<p class="text-gray-600">{{ error.statusMessage || "An error occurred" }}</p>
<button
@click="handleClearError"
class="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Go Home
</button>
</div>
</template>
.env.example to .env and fill in valuesnpm installnpm run dev# Development
npm run dev # Start dev server (http://localhost:3000)
# Build
npm run build # Build for production (SSR)
npm run generate # Static site generation (SSG)
# Preview
npm run preview # Preview production build locally
# Lint
npx nuxi module add @nuxt/eslint # Add ESLint module (if not added)
npm run lint # Run ESLint
# Type check
npx nuxi typecheck
# Add module
npx nuxi module add <module-name>
# Clean
npx nuxi cleanup # Remove .nuxt, .output, node_modules/.cache
@nuxt/test-utils with Vitest for component and integration testing. Install with npm install -D @nuxt/test-utils vitest @vue/test-utils.useState composable is Nuxt's built-in SSR-safe state. For more complex state, add Pinia via @pinia/nuxt module.server/utils/db.ts.nuxt-auth-utils module or implement custom auth with server routes + cookies. The middleware pattern shown above handles route protection.@nuxt/ui) provides Tailwind-based components built for Nuxt. Alternatively use PrimeVue or Radix Vue.@nuxt/content module for Markdown/MDX-based content. Great for blogs and documentation.@nuxt/image module for automatic image optimization with <NuxtImg> and <NuxtPicture> components.useSeoMeta() composable and useHead() for per-page meta tags. Nuxt handles SSR meta automatically.nitro.preset in nuxt.config.ts.testing
Set up Vitest 2.x with TypeScript for unit and component testing using test/describe/it, vi.fn/vi.mock/vi.spyOn, component testing with Testing Library, coverage (v8/istanbul), workspace config, and snapshot testing.
testing
Set up pytest 8.x with Python for unit and integration testing using fixtures (scope, autouse, parametrize), async tests (pytest-asyncio), mocking (unittest.mock, pytest-mock), coverage (pytest-cov), conftest.py patterns, and markers.
testing
Set up Playwright 1.49+ with TypeScript for E2E testing using page object model, fixtures, test.describe/test blocks, assertions, selectors, network mocking, CI configuration, and trace viewer.
testing
Set up Jest 30+ with TypeScript for unit tests, integration tests, mocking (jest.fn, jest.mock, jest.spyOn), coverage configuration, custom matchers, snapshot testing, and setup/teardown patterns.