CtrlK
BlogDocsLog inGet started
Tessl Logo

nuxtjs-project-starter

Scaffold a Nuxt 3.x project with auto-imports, layouts, `definePageMeta`, `useFetch`/`useAsyncData`, server routes, Nitro config, and SSR/SPA mode conventions.

79

Quality

71%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Optimize this skill with Tessl

npx tessl skill review --optimize ./frontend/nuxtjs-project-starter/SKILL.md
SKILL.md
Quality
Evals
Security

Nuxt.js Project Starter

Scaffold a Nuxt 3.x project with auto-imports, layouts, definePageMeta, useFetch/useAsyncData, server routes, Nitro config, and SSR/SPA mode conventions.

Prerequisites

  • Node.js >= 20.x
  • npm >= 10.x (or pnpm >= 9.x)
  • Git

Scaffold Command

npx nuxi@latest init my-app
cd my-app
npm install
npx nuxi module add eslint
npm install tailwindcss @tailwindcss/vite

Project Structure

├── 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

Key Conventions

  • Auto-imports: Vue APIs (ref, computed, watch), composables in composables/, components in components/, and utilities in utils/ are all auto-imported. No manual imports needed.
  • File-based routing: files in 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: wrap pages with layouts defined in layouts/. Default layout applies unless overridden via definePageMeta.
  • Server routes: files in server/api/ become API endpoints. Use method suffixes (.get.ts, .post.ts) or handle methods in a single file.
  • Nitro server engine: server routes run on Nitro, which is framework-agnostic and deploy-anywhere.
  • SSR by default: pages render on the server. Use ssr: false in config for SPA mode, or per-route with routeRules.

Essential Patterns

Nuxt Config (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 },
});

Tailwind CSS Entry (app/assets/css/main.css)

@import "tailwindcss";

Root App Component (app/app.vue)

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

Default Layout (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>

Auth Layout (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>

Page with 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>

Data Fetching: 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>

Dynamic Route Page

<!-- 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>

Route Middleware

// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
  const authStore = useAuthStore();

  if (!authStore.isAuthenticated) {
    return navigateTo("/login");
  }
});

Server API Route (GET)

// 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 Route (POST with Validation)

// 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 Route (Dynamic Param)

// 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;
});

Composable (Auto-imported)

// 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 };
}

Nuxt Plugin

// 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
  }
});

Error Page

<!-- 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>

First Steps After Scaffold

  1. Copy .env.example to .env and fill in values
  2. Install dependencies: npm install
  3. Start dev server: npm run dev
  4. Verify the app loads at http://localhost:3000
  5. Open Nuxt DevTools in the browser to confirm modules are loaded

Common Commands

# 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

Integration Notes

  • Testing: use @nuxt/test-utils with Vitest for component and integration testing. Install with npm install -D @nuxt/test-utils vitest @vue/test-utils.
  • State management: useState composable is Nuxt's built-in SSR-safe state. For more complex state, add Pinia via @pinia/nuxt module.
  • Database: Nitro server routes can connect directly to databases. Pair with Prisma or Drizzle ORM. Import the client in server/utils/db.ts.
  • Auth: pair with nuxt-auth-utils module or implement custom auth with server routes + cookies. The middleware pattern shown above handles route protection.
  • UI components: Nuxt UI (@nuxt/ui) provides Tailwind-based components built for Nuxt. Alternatively use PrimeVue or Radix Vue.
  • CMS/Content: @nuxt/content module for Markdown/MDX-based content. Great for blogs and documentation.
  • Image optimization: @nuxt/image module for automatic image optimization with <NuxtImg> and <NuxtPicture> components.
  • SEO: use useSeoMeta() composable and useHead() for per-page meta tags. Nuxt handles SSR meta automatically.
  • Deployment: Nitro supports presets for Vercel, Netlify, Cloudflare Workers, AWS Lambda, Deno, Bun, and Node.js. Set nitro.preset in nuxt.config.ts.
Repository
achreftlili/deep-dev-skills
Last updated
Created

Is this your skill?

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.