用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill vue-nuxt-expert命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | vue-nuxt-expert |
| description | Use when implementing vue nuxt functionality with production-grade patterns and safeguards. |
| metadata | {"author":"0xharryriddle"} |
Before implementing any Nuxt.js features, you MUST fetch the latest documentation to ensure you're using current best practices:
/nuxt/nuxtExample 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.
Before implementing any Nuxt.js features, you:
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]
// 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: },
: [
{ : },
{ : , : },
],
}
},
: {
: {
: [, ]
}
}
})
<!-- 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/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,
: product. > ,
: product.
}
})
() {
cached = ().()
(cached) {
cached
}
product = ()..({
: { id }
})
(product && options?.) {
().(
,
product,
{ : options. }
)
}
product
}
// 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)
db = ()
product = db..({
: {
...body,
: user.
}
})
().()
({
: user.,
: ,
: product.
})
product
})
() {
token = (event, ) || (event, )?.(, )
(!token) {
({
: ,
:
})
}
{
payload = jwt.(token, ().)
(payload.)
} (error) {
({
: ,
:
})
}
}
// 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)
}
(()..) {
$fetch(, {
: ,
: { : items. }
})
}
}
() {
items. = items..(
item.. !== productId
)
}
() {
items. = []
}
( ()., (isAuth) => {
(isAuth) {
{ data } = $fetch()
(data?.) {
items. = data.
}
}
})
{
: (items),
: (itemCount),
: (total),
addItem,
removeItem,
clearCart
}
}
// 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, ?: ) => (url, { ...options, : , body }),
: (url, { ...options, : , body }),
: (url, { ...options, : }),
}
}
// 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}`)
}
})
// 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.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)
})
})
// 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}`)
}
<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>
<template>
<div>
<LazyHeavyComponent v-if="showComponent" />
<button @click="showComponent = true">Load Component</button>
</div>
</template>
# 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 — distributed by TomeVault.