Scaffold a Nuxt 3.x project with auto-imports, layouts, `definePageMeta`, `useFetch`/`useAsyncData`, server routes, Nitro config, and SSR/SPA mode conventions.
79
71%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./frontend/nuxtjs-project-starter/SKILL.mdScaffold 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 templateref, 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.181fcbc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.