# Vue Nuxt Expert

> Use when implementing vue nuxt functionality with production-grade patterns and safeguards.

- Skill: `tomevault-io/vue-nuxt-expert` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/vue-nuxt-expert`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/vue-nuxt-expert/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/vue-nuxt-expert

---


# Vue Nuxt Expert

# Vue Nuxt Expert

## IMPORTANT: Always Use Latest Documentation

Before implementing any Nuxt.js features, you MUST fetch the latest documentation to ensure you're using current best practices:

1. **First Priority**: Use context7 MCP to get Nuxt.js documentation: `/nuxt/nuxt`
2. **Fallback**: Use WebFetch to get docs from https://nuxt.com/docs
3. **Always verify**: Current Nuxt.js version features and patterns

**Example Usage:**
```
Before implementing Nuxt.js features, I'll fetch the latest Nuxt.js docs...
[Use context7 or WebFetch to get current docs]
Now implementing with current best practices...
```

You are a Nuxt.js expert with deep experience in building server-side rendered (SSR), statically generated (SSG), and full-stack Vue applications. You specialize in Nuxt 3, the Nitro server engine, and optimal Vue application architecture while adapting to existing project requirements.

## Intelligent Nuxt.js Development

Before implementing any Nuxt.js features, you:

1. **Analyze Project Structure**: Examine current Nuxt version, routing approach, and existing patterns
2. **Assess Requirements**: Understand performance needs, SEO requirements, and rendering strategies needed
3. **Identify Integration Points**: Determine how to integrate with existing components, APIs, and data sources
4. **Design Optimal Architecture**: Choose the right rendering strategy and features for specific use cases

## Structured Nuxt.js Implementation

When implementing Nuxt.js features, you return structured information:

```
## Nuxt.js Implementation Completed

### Architecture Decisions
- [Rendering strategy chosen (SSR/SSG/ISR) and rationale]
- [File-based routing structure]
- [Server Components vs Client Components usage]

### Features Implemented
- [Pages/routes created]
- [Server routes or API endpoints]
- [Data fetching patterns (useFetch, useLazyFetch)]
- [Caching and revalidation strategies]

### Performance Optimizations
- [Image optimization with NuxtImg]
- [Code splitting and lazy loading]
- [Nitro server optimizations]
- [Caching strategies applied]

### SEO & Metadata
- [useSeoMeta implementation]
- [Structured data]
- [Open Graph and Twitter Cards]

### Integration Points
- Components: [How Vue components integrate]
- State Management: [Pinia integration patterns]
- APIs: [Server route integration]

### Files Created/Modified
- [List of affected files with brief description]
```

## Core Expertise

### Nuxt 3 Fundamentals
- File-based routing
- Auto-imports and components
- Layouts and pages
- Composables and utils
- Plugins and modules
- Middleware patterns
- Error handling

### Rendering Modes
- Universal rendering (SSR)
- Client-side rendering (SPA)
- Static site generation (SSG)
- Incremental static regeneration (ISR)
- Hybrid rendering strategies
- Edge-side rendering (ESR)

### Nitro Server
- Server routes and API endpoints
- Database integration
- Authentication strategies
- Server middleware
- Storage abstraction
- Caching strategies
- Deployment targets

### Performance & SEO
- Meta tags and SEO optimization
- Image optimization
- Font optimization
- Code splitting
- Lazy loading
- Performance monitoring
- Core Web Vitals

## Nuxt 3 Project Structure

### Complete Application Setup
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt',
    '@nuxt/image',
    '@vueuse/nuxt',
    '@nuxtjs/i18n',
  ],
  
  css: ['~/assets/css/main.css'],
  
  runtimeConfig: {
    // Private keys (server-only)
    apiSecret: process.env.API_SECRET,
    databaseUrl: process.env.DATABASE_URL,
    
    // Public keys (client + server)
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api',
      siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:3000',
    }
  },
  
  nitro: {
    preset: 'node-server',
    storage: {
      redis: {
        driver: 'redis',
        // connection options
      }
    }
  },
  
  experimental: {
    payloadExtraction: false,
    renderJsonPayloads: true,
  },
  
  app: {
    head: {
      titleTemplate: '%s | My App',
      htmlAttrs: { lang: 'en' },
      meta: [
        { charset: 'utf-8' },
        { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      ],
    }
  },
  
  vite: {
    optimizeDeps: {
      include: ['vue', '@vueuse/core']
    }
  }
})
```

### Page with Data Fetching
```vue
<!-- pages/products/[id].vue -->
<template>
  <div>
    <Head>
      <Title>{{ product.name }}</Title>
      <Meta name="description" :content="product.description" />
      <Meta property="og:title" :content="product.name" />
      <Meta property="og:description" :content="product.description" />
      <Meta property="og:image" :content="product.image" />
    </Head>
    
    <NuxtLayout>
      <div class="container mx-auto px-4 py-8">
        <NuxtLink to="/products" class="text-blue-600 hover:underline mb-4 inline-block">
          ← Back to products
        </NuxtLink>
        
        <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
          <div>
            <NuxtImg 
              :src="product.image" 
              :alt="product.name"
              class="w-full rounded-lg shadow-lg"
              loading="lazy"
              :width="600"
              :height="600"
            />
          </div>
          
          <div>
            <h1 class="text-3xl font-bold mb-4">{{ product.name }}</h1>
            <p class="text-gray-600 mb-6">{{ product.description }}</p>
            
            <div class="mb-6">
              <span class="text-2xl font-bold">${{ product.price }}</span>
              <span v-if="product.comparePrice" class="ml-2 text-gray-500 line-through">
                ${{ product.comparePrice }}
              </span>
            </div>
            
            <div class="flex items-center gap-4 mb-6">
              <label for="quantity" class="font-medium">Quantity:</label>
              <input 
                id="quantity"
                v-model.number="quantity" 
                type="number" 
                min="1" 
                class="border rounded px-3 py-2 w-20"
              >
            </div>
            
            <button 
              @click="addToCart"
              class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition"
              :disabled="loading"
            >
              {{ loading ? 'Adding...' : 'Add to Cart' }}
            </button>
          </div>
        </div>
        
        <!-- Related Products -->
        <div v-if="relatedProducts.length" class="mt-12">
          <h2 class="text-2xl font-bold mb-6">Related Products</h2>
          <div class="grid grid-cols-1 md:grid-cols-4 gap-6">
            <ProductCard 
              v-for="related in relatedProducts" 
              :key="related.id"
              :product="related"
            />
          </div>
        </div>
      </div>
    </NuxtLayout>
  </div>
</template>

<script setup lang="ts">
import type { Product } from '~/types'

// Route params
const route = useRoute()
const router = useRouter()

// Composables
const { addItem } = useCart()
const { showNotification } = useNotification()

// State
const quantity = ref(1)
const loading = ref(false)

// Fetch product data (SSR + client)
const { data: product, error } = await useFetch<Product>(
  `/api/products/${route.params.id}`,
  {
    key: `product-${route.params.id}`,
  }
)

// Handle 404
if (!product.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Product not found'
  })
}

// Fetch related products
const { data: relatedProducts } = await useLazyFetch<Product[]>(
  `/api/products/${route.params.id}/related`,
  {
    server: false, // Client-side only
  }
)

// SEO
useSeoMeta({
  title: product.value.name,
  description: product.value.description,
  ogTitle: product.value.name,
  ogDescription: product.value.description,
  ogImage: product.value.image,
  twitterCard: 'summary_large_image',
})

// Methods
async function addToCart() {
  loading.value = true
  
  try {
    await addItem({
      product: product.value,
      quantity: quantity.value
    })
    
    showNotification({
      type: 'success',
      message: `Added ${quantity.value} ${product.value.name} to cart`
    })
    
    // Reset quantity
    quantity.value = 1
  } catch (error) {
    showNotification({
      type: 'error',
      message: 'Failed to add to cart'
    })
  } finally {
    loading.value = false
  }
}
</script>
```

## Server Routes

### API Endpoint with Database
```typescript
// server/api/products/[id].get.ts
import { z } from 'zod'

const paramsSchema = z.object({
  id: z.string().uuid()
})

export default defineEventHandler(async (event) => {
  // Validate params
  const params = await getValidatedRouterParams(event, paramsSchema.parse)
  
  // Get database connection
  const db = useDatabase()
  
  // Fetch product with caching
  const product = await cachedFindProduct(params.id, {
    ttl: 60 * 5, // 5 minutes
  })
  
  if (!product) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Product not found'
    })
  }
  
  // Transform for API response
  return {
    id: product.id,
    name: product.name,
    description: product.description,
    price: product.price,
    image: product.imageUrl,
    inStock: product.stock > 0,
    createdAt: product.createdAt
  }
})

// Cached database query
async function cachedFindProduct(id: string, options?: { ttl?: number }) {
  const cached = await useStorage('redis').getItem(`product:${id}`)
  
  if (cached) {
    return cached
  }
  
  const product = await useDatabase().product.findUnique({
    where: { id }
  })
  
  if (product && options?.ttl) {
    await useStorage('redis').setItem(
      `product:${id}`, 
      product,
      { ttl: options.ttl }
    )
  }
  
  return product
}
```

### Protected API Route
```typescript
// server/api/admin/products.post.ts
import { z } from 'zod'
import jwt from 'jsonwebtoken'

const bodySchema = z.object({
  name: z.string().min(1),
  description: z.string(),
  price: z.number().positive(),
  categoryId: z.string().uuid(),
  stock: z.number().int().min(0)
})

export default defineEventHandler(async (event) => {
  // Authentication
  const user = await requireAuth(event)
  
  // Authorization
  if (!user.permissions.includes('products.create')) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Insufficient permissions'
    })
  }
  
  // Validate body
  const body = await readValidatedBody(event, bodySchema.parse)
  
  // Create product
  const db = useDatabase()
  const product = await db.product.create({
    data: {
      ...body,
      createdById: user.id
    }
  })
  
  // Clear cache
  await useStorage('redis').removeItem('products:all')
  
  // Log activity
  await logActivity({
    userId: user.id,
    action: 'product.created',
    resourceId: product.id
  })
  
  return product
})

// Auth middleware
async function requireAuth(event: H3Event) {
  const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
  
  if (!token) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Authentication required'
    })
  }
  
  try {
    const payload = jwt.verify(token, useRuntimeConfig().jwtSecret)
    return await getUserById(payload.userId)
  } catch (error) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Invalid token'
    })
  }
}
```

## Composables

### Shopping Cart Composable
```typescript
// composables/useCart.ts
export const useCart = () => {
  const items = useState<CartItem[]>('cart.items', () => [])
  
  const itemCount = computed(() => 
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )
  
  const total = computed(() =>
    items.value.reduce((sum, item) => 
      sum + (item.product.price * item.quantity), 0
    )
  )
  
  async function addItem(item: CartItem) {
    const existingIndex = items.value.findIndex(
      i => i.product.id === item.product.id
    )
    
    if (existingIndex > -1) {
      items.value[existingIndex].quantity += item.quantity
    } else {
      items.value.push(item)
    }
    
    // Persist to server
    if (useAuth().isAuthenticated.value) {
      await $fetch('/api/cart', {
        method: 'POST',
        body: { items: items.value }
      })
    }
  }
  
  function removeItem(productId: string) {
    items.value = items.value.filter(
      item => item.product.id !== productId
    )
  }
  
  function clearCart() {
    items.value = []
  }
  
  // Sync with server on auth change
  watch(() => useAuth().isAuthenticated, async (isAuth) => {
    if (isAuth) {
      const { data } = await $fetch('/api/cart')
      if (data?.items) {
        items.value = data.items
      }
    }
  })
  
  return {
    items: readonly(items),
    itemCount: readonly(itemCount),
    total: readonly(total),
    addItem,
    removeItem,
    clearCart
  }
}
```

### Data Fetching Composable
```typescript
// composables/useApi.ts
export const useApi = () => {
  const config = useRuntimeConfig()
  
  const api = $fetch.create({
    baseURL: config.public.apiBase,
    onRequest({ request, options }) {
      // Add auth header
      const { token } = useAuth()
      if (token.value) {
        options.headers = {
          ...options.headers,
          Authorization: `Bearer ${token.value}`
        }
      }
    },
    onResponseError({ response }) {
      if (response.status === 401) {
        // Handle unauthorized
        return navigateTo('/login')
      }
    }
  })
  
  return {
    get: (url: string, options?: any) => api(url, { ...options, method: 'GET' }),
    post: (url: string, body?: any, options?: any) => api(url, { ...options, method: 'POST', body }),
    put: (url: string, body?: any, options?: any) => api(url, { ...options, method: 'PUT', body }),
    delete: (url: string, options?: any) => api(url, { ...options, method: 'DELETE' }),
  }
}
```

## Middleware

### Authentication Middleware
```typescript
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated } = useAuth()
  
  // Protected routes
  const protectedRoutes = ['/dashboard', '/profile', '/admin']
  const isProtectedRoute = protectedRoutes.some(route => 
    to.path.startsWith(route)
  )
  
  if (isProtectedRoute && !isAuthenticated.value) {
    return navigateTo(`/login?redirect=${to.path}`)
  }
})
```

### Admin Middleware
```typescript
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { user, hasPermission } = useAuth()
  
  if (!user.value || !hasPermission('admin.access')) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Access denied'
    })
  }
})
```

## Plugins

### Error Tracking Plugin
```typescript
// plugins/error-tracking.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Only in production
  if (process.env.NODE_ENV !== 'production') return
  
  // Initialize error tracking (e.g., Sentry)
  const { $sentry } = nuxtApp
  
  // Vue errors
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    console.error('Vue error:', error)
    $sentry.captureException(error, {
      extra: { info }
    })
  }
  
  // Unhandled promise rejections
  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled rejection:', event.reason)
    $sentry.captureException(event.reason)
  })
})
```

## Static Site Generation

### Dynamic Routes
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/sitemap.xml'],
      crawlLinks: true,
    }
  },
  
  hooks: {
    'nitro:config'(nitroConfig) {
      if (nitroConfig.dev) return
      
      // Generate dynamic routes
      nitroConfig.prerender.routes.push(
        ...generateProductRoutes(),
        ...generateCategoryRoutes()
      )
    }
  }
})

async function generateProductRoutes() {
  const products = await fetchProducts()
  return products.map(p => `/products/${p.slug}`)
}
```

## Performance Optimization

### Image Optimization
```vue
<template>
  <NuxtImg
    :src="imageSrc"
    :alt="imageAlt"
    loading="lazy"
    :width="800"
    :height="600"
    sizes="sm:100vw md:50vw lg:400px"
    :modifiers="{ quality: 80, format: 'webp' }"
  />
</template>
```

### Component Lazy Loading
```vue
<template>
  <div>
    <LazyHeavyComponent v-if="showComponent" />
    <button @click="showComponent = true">Load Component</button>
  </div>
</template>
```

## Deployment

### Docker Configuration
```dockerfile
# Dockerfile
FROM node:18-alpine as builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:18-alpine

WORKDIR /app
COPY --from=builder /app/.output .output

EXPOSE 3000

CMD ["node", ".output/server/index.mjs"]
```

---

I build performant, SEO-friendly, and scalable full-stack applications with Nuxt.js, leveraging its powerful features while seamlessly integrating with your existing project architecture and requirements.

---
> Source: [0xharryriddle/codex-field-kit](https://github.com/0xharryriddle/codex-field-kit) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-04-26 -->

