nuxt-website
1
总安装量
1
周安装量
#77822
全站排名
安装命令
npx skills add https://github.com/gitarbor/gitarbor-tui --skill nuxt-website
Agent 安装分布
amp
1
cline
1
opencode
1
cursor
1
continue
1
kimi-cli
1
Skill 文档
When to use this skill
- User asks to update, modify, or build the website/marketing site/landing page/docs site
- Working with Vue 3 components or Nuxt 4 features in the website directory
- Creating or editing website pages, layouts, or UI components
- Managing website content, SEO, meta tags, or Open Graph data
- Configuring Nuxt plugins, modules, middleware, or nuxt.config.ts
- Working with Nuxt composables, auto-imports, or data fetching (useFetch, useAsyncData)
- Optimizing website performance, images, or assets
- Setting up or modifying website deployment configurations
- Implementing forms, navigation, or interactive features on the website
- Working with API routes in the server directory
- User explicitly mentions “website”, “marketing site”, “landing page”, “docs”, “Vue”, “Nuxt”, or references website-specific files/paths
What this skill does
This skill provides comprehensive guidance for building and maintaining marketing websites using Vue 3 and Nuxt 4, including:
- Component development and composition
- Page and layout management
- Content management and SEO optimization
- Nuxt configuration and module setup
- Performance optimization
- Deployment strategies
Nuxt 4 Project Structure
website/
âââ .nuxt/ # Generated by Nuxt (ignore)
âââ .output/ # Build output (ignore)
âââ node_modules/ # Dependencies (ignore)
âââ assets/ # Uncompiled assets (CSS, images, fonts)
â âââ css/ # Global styles, variables
â âââ images/ # Image assets
â âââ fonts/ # Custom fonts
âââ components/ # Vue components (auto-imported)
â âââ ui/ # UI components (Button, Card, etc.)
â âââ layout/ # Layout components (Header, Footer, etc.)
â âââ content/ # Content components (Hero, Features, etc.)
âââ composables/ # Composable functions (auto-imported)
â âââ useApi.ts # Example: API composable
âââ layouts/ # Layout templates
â âââ default.vue # Default layout
âââ middleware/ # Route middleware
âââ pages/ # File-based routing
â âââ index.vue # Home page (/)
â âââ about.vue # About page (/about)
â âââ [...slug].vue # Catch-all route
âââ plugins/ # Nuxt plugins
âââ public/ # Static assets (served as-is)
â âââ favicon.ico
â âââ robots.txt
âââ server/ # Server routes and middleware
â âââ api/ # API endpoints
â âââ middleware/ # Server middleware
âââ utils/ # Utility functions (auto-imported)
âââ app.vue # Root component
âââ nuxt.config.ts # Nuxt configuration
âââ package.json # Dependencies
âââ tsconfig.json # TypeScript config
Component Development
Component File Structure
Use the Composition API with <script setup>:
<script setup lang="ts">
// Props
interface Props {
title: string
description?: string
}
const props = withDefaults(defineProps<Props>(), {
description: ''
})
// Composables (auto-imported)
const router = useRouter()
const route = useRoute()
// Local state
const isVisible = ref(false)
// Computed
const fullTitle = computed(() => `${props.title} - Marketing`)
// Methods
const handleClick = () => {
isVisible.value = !isVisible.value
}
// Lifecycle
onMounted(() => {
console.log('Component mounted')
})
</script>
<template>
<div class="component">
<h1>{{ fullTitle }}</h1>
<p v-if="description">{{ description }}</p>
<button @click="handleClick">Toggle</button>
</div>
</template>
<style scoped>
.component {
padding: 2rem;
}
h1 {
font-size: 2rem;
font-weight: bold;
}
</style>
Component Naming
- Use PascalCase for component files:
HeroSection.vue,FeatureCard.vue - Auto-import uses PascalCase in templates:
<HeroSection />,<FeatureCard /> - Organize by type:
components/ui/,components/layout/,components/content/
Component Best Practices
- Props: Use TypeScript interfaces for type safety
- Emits: Define emits explicitly with TypeScript
- Slots: Use named slots for flexibility
- Composables: Extract reusable logic to composables
- Auto-imports: All components in
components/are auto-imported - Scoped styles: Use
<style scoped>to avoid style conflicts
Page Development
Creating Pages
Pages in pages/ directory are automatically routed:
<!-- pages/index.vue -> / -->
<script setup lang="ts">
// SEO with useHead
useHead({
title: 'Home - Marketing Site',
meta: [
{ name: 'description', content: 'Welcome to our marketing site' },
{ property: 'og:title', content: 'Home - Marketing Site' },
{ property: 'og:description', content: 'Welcome to our marketing site' }
]
})
// Data fetching
const { data: features } = await useFetch('/api/features')
</script>
<template>
<div>
<HeroSection
title="Welcome to Our Product"
subtitle="The best solution for your needs"
/>
<FeaturesList :features="features" />
<CallToAction />
</div>
</template>
Dynamic Routes
pages/
âââ blog/
â âââ index.vue # /blog
â âââ [slug].vue # /blog/:slug
â âââ [...slug].vue # /blog/* (catch-all)
âââ products/
âââ [id].vue # /products/:id
Example dynamic page:
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug
const { data: post } = await useFetch(`/api/blog/${slug}`)
useHead({
title: `${post.value?.title} - Blog`,
meta: [
{ name: 'description', content: post.value?.excerpt }
]
})
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<div v-html="post.content" />
</article>
</template>
SEO and Meta Tags
Using useHead
<script setup lang="ts">
useHead({
title: 'Page Title',
titleTemplate: '%s - Marketing Site', // Optional template
meta: [
{ name: 'description', content: 'Page description' },
{ name: 'keywords', content: 'keyword1, keyword2' },
// Open Graph
{ property: 'og:title', content: 'Page Title' },
{ property: 'og:description', content: 'Page description' },
{ property: 'og:image', content: '/images/og-image.jpg' },
{ property: 'og:url', content: 'https://example.com/page' },
// Twitter
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: 'Page Title' },
{ name: 'twitter:description', content: 'Page description' },
{ name: 'twitter:image', content: '/images/twitter-image.jpg' }
],
link: [
{ rel: 'canonical', href: 'https://example.com/page' }
]
})
</script>
useSeoMeta Composable
Preferred for SEO (better type safety):
<script setup lang="ts">
useSeoMeta({
title: 'Page Title',
description: 'Page description',
ogTitle: 'Page Title',
ogDescription: 'Page description',
ogImage: '/images/og-image.jpg',
ogUrl: 'https://example.com/page',
twitterCard: 'summary_large_image',
twitterTitle: 'Page Title',
twitterDescription: 'Page description',
twitterImage: '/images/twitter-image.jpg'
})
</script>
Layouts
Creating Layouts
<!-- layouts/default.vue -->
<script setup lang="ts">
const route = useRoute()
</script>
<template>
<div class="layout">
<SiteHeader />
<main>
<slot /> <!-- Page content -->
</main>
<SiteFooter />
</div>
</template>
<style scoped>
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
</style>
Using Layouts in Pages
<!-- pages/about.vue -->
<script setup lang="ts">
definePageMeta({
layout: 'default' // Use specific layout
})
</script>
<template>
<div>About page content</div>
</template>
Nuxt Configuration
nuxt.config.ts
export default defineNuxtConfig({
// Development
devtools: { enabled: true },
// TypeScript
typescript: {
strict: true,
typeCheck: true
},
// Modules
modules: [
'@nuxtjs/tailwindcss', // Tailwind CSS
'@nuxt/image', // Image optimization
'@nuxtjs/seo', // SEO utilities
],
// App config
app: {
head: {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
htmlAttrs: {
lang: 'en'
},
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
}
},
// CSS
css: [
'~/assets/css/main.css'
],
// Runtime config (environment variables)
runtimeConfig: {
// Private keys (server-side only)
apiSecret: process.env.API_SECRET,
// Public keys (client-side)
public: {
apiBase: process.env.API_BASE_URL || 'https://api.example.com',
siteUrl: process.env.SITE_URL || 'https://example.com'
}
},
// Nitro (server) config
nitro: {
preset: 'node-server', // or 'vercel', 'netlify', etc.
compressPublicAssets: true
},
// Build optimization
vite: {
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'vue-router']
}
}
}
}
}
})
Composables
Creating Composables
Composables in composables/ are auto-imported:
// composables/useApi.ts
export const useApi = () => {
const config = useRuntimeConfig()
const baseUrl = config.public.apiBase
const fetchData = async <T>(endpoint: string): Promise<T> => {
const { data, error } = await useFetch<T>(`${baseUrl}${endpoint}`)
if (error.value) {
throw new Error(`API Error: ${error.value.message}`)
}
return data.value as T
}
return {
fetchData
}
}
Usage in components:
<script setup lang="ts">
const api = useApi()
const { data: products } = await api.fetchData('/products')
</script>
Common Composables
useRoute()– Access current routeuseRouter()– Navigate programmaticallyuseFetch()– Data fetching with SSR supportuseAsyncData()– Advanced data fetchinguseState()– Shared state across componentsuseHead()– Manage head tagsuseSeoMeta()– Manage SEO meta tagsuseRuntimeConfig()– Access runtime config
Data Fetching
useFetch
<script setup lang="ts">
// Simple fetch
const { data } = await useFetch('/api/data')
// With options
const { data, pending, error, refresh } = await useFetch('/api/data', {
method: 'GET',
query: { page: 1 },
headers: { 'Authorization': 'Bearer token' },
lazy: false, // Wait for data before rendering
server: true, // Fetch on server-side
pick: ['id', 'name'] // Pick specific fields
})
// Refresh data
const handleRefresh = () => refresh()
</script>
useAsyncData
For custom async operations:
<script setup lang="ts">
const { data, pending } = await useAsyncData('unique-key', async () => {
// Custom async logic
const response = await $fetch('/api/data')
return response.items.map(item => ({
id: item.id,
name: item.name.toUpperCase()
}))
})
</script>
Styling
Global Styles
/* assets/css/main.css */
:root {
--color-primary: #0070f3;
--color-secondary: #7928ca;
--spacing-unit: 1rem;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
line-height: 1.6;
color: #333;
}
Scoped Component Styles
<style scoped>
/* Only applies to this component */
.card {
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Deep selector for child components */
:deep(.child-class) {
color: red;
}
/* Slotted content */
:slotted(.slot-class) {
font-weight: bold;
}
</style>
CSS Modules
<style module>
.card {
padding: 2rem;
}
</style>
<template>
<div :class="$style.card">
Content
</div>
</template>
Performance Optimization
Image Optimization
Use @nuxt/image module:
<template>
<!-- Optimized image with auto-format and responsive -->
<NuxtImg
src="/images/hero.jpg"
alt="Hero image"
width="1200"
height="600"
format="webp"
quality="80"
loading="lazy"
/>
<!-- Picture element with multiple formats -->
<NuxtPicture
src="/images/hero.jpg"
alt="Hero image"
:img-attrs="{ class: 'hero-image' }"
/>
</template>
Lazy Loading Components
<script setup lang="ts">
// Lazy load component
const LazyComponent = defineAsyncComponent(() =>
import('~/components/HeavyComponent.vue')
)
</script>
<template>
<LazyComponent v-if="shouldShow" />
</template>
Or use Nuxt’s Lazy prefix:
<template>
<!-- Automatically lazy-loaded -->
<LazyHeavyComponent />
</template>
Code Splitting
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
if (id.includes('node_modules')) {
return 'vendor'
}
if (id.includes('components/ui')) {
return 'ui'
}
}
}
}
}
}
})
Middleware
Route Middleware
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const user = useState('user')
if (!user.value && to.path !== '/login') {
return navigateTo('/login')
}
})
Usage:
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
</script>
Global Middleware
// middleware/analytics.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
// Runs on every route change
console.log('Navigating to:', to.path)
})
Plugins
Creating Plugins
// plugins/analytics.client.ts
export default defineNuxtPlugin((nuxtApp) => {
// Only runs on client-side
nuxtApp.hook('page:finish', () => {
// Track page view
console.log('Page view:', nuxtApp.$router.currentRoute.value.path)
})
return {
provide: {
analytics: {
track: (event: string) => {
console.log('Track event:', event)
}
}
}
}
})
Usage in components:
<script setup lang="ts">
const { $analytics } = useNuxtApp()
const handleClick = () => {
$analytics.track('button_click')
}
</script>
API Routes
Creating API Endpoints
// server/api/hello.get.ts
export default defineEventHandler((event) => {
return {
message: 'Hello from API',
timestamp: Date.now()
}
})
// server/api/users/[id].get.ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return {
id,
name: 'User Name'
}
})
// server/api/contact.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Validate and process
if (!body.email) {
throw createError({
statusCode: 400,
statusMessage: 'Email is required'
})
}
return {
success: true,
message: 'Contact form submitted'
}
})
Deployment
Build Commands
# Development
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
# Generate static site (if applicable)
npm run generate
Environment Variables
# .env
API_SECRET=secret123
API_BASE_URL=https://api.example.com
SITE_URL=https://example.com
Access in Nuxt:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: process.env.API_SECRET,
public: {
apiBase: process.env.API_BASE_URL
}
}
})
Vercel Deployment
// vercel.json
{
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"framework": "nuxtjs"
}
Netlify Deployment
# netlify.toml
[build]
command = "npm run build"
publish = ".output/public"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Docker Deployment
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
Common Patterns
Loading States
<script setup lang="ts">
const { data, pending, error } = await useFetch('/api/data')
</script>
<template>
<div>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else>{{ data }}</div>
</div>
</template>
Form Handling
<script setup lang="ts">
const form = reactive({
name: '',
email: '',
message: ''
})
const pending = ref(false)
const error = ref<string | null>(null)
const success = ref(false)
const handleSubmit = async () => {
pending.value = true
error.value = null
try {
const { data } = await useFetch('/api/contact', {
method: 'POST',
body: form
})
success.value = true
// Reset form
Object.assign(form, { name: '', email: '', message: '' })
} catch (e) {
error.value = e instanceof Error ? e.message : 'Something went wrong'
} finally {
pending.value = false
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" placeholder="Name" required />
<input v-model="form.email" type="email" placeholder="Email" required />
<textarea v-model="form.message" placeholder="Message" required />
<button type="submit" :disabled="pending">
{{ pending ? 'Sending...' : 'Send' }}
</button>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="success" class="success">Message sent!</div>
</form>
</template>
Pagination
<script setup lang="ts">
const page = ref(1)
const pageSize = 10
const { data: items } = await useFetch('/api/items', {
query: {
page,
limit: pageSize
},
watch: [page] // Re-fetch when page changes
})
const nextPage = () => page.value++
const prevPage = () => page.value = Math.max(1, page.value - 1)
</script>
<template>
<div>
<div v-for="item in items?.data" :key="item.id">
{{ item.name }}
</div>
<button @click="prevPage" :disabled="page === 1">Previous</button>
<span>Page {{ page }}</span>
<button @click="nextPage">Next</button>
</div>
</template>
Best Practices
-
Component Organization
- Keep components small and focused
- Use composition over inheritance
- Extract reusable logic to composables
-
Performance
- Use lazy loading for heavy components
- Optimize images with
@nuxt/image - Implement proper caching strategies
- Use
useFetchover manualfetchfor SSR support
-
SEO
- Use
useSeoMetafor all pages - Add Open Graph and Twitter Card meta tags
- Include canonical URLs
- Generate sitemap and robots.txt
- Use
-
Type Safety
- Enable strict TypeScript mode
- Define interfaces for all data structures
- Use typed composables and utilities
-
State Management
- Use
useStatefor simple shared state - Consider Pinia for complex state management
- Keep state close to where it’s used
- Use
-
Error Handling
- Always handle errors in async operations
- Use
createErrorfor API routes - Provide user-friendly error messages
-
Accessibility
- Use semantic HTML elements
- Add ARIA labels where needed
- Ensure keyboard navigation works
- Test with screen readers
-
Security
- Validate all user inputs
- Sanitize content before rendering
- Use environment variables for secrets
- Implement rate limiting for API routes
Troubleshooting
Common Issues
Auto-imports not working:
- Restart dev server
- Check file naming (must be in correct directory)
- Clear
.nuxtdirectory and rebuild
SSR errors:
- Check for browser-only code (use
process.clientguard) - Ensure data is available before rendering
- Use
<ClientOnly>component for client-only components
Build errors:
- Clear
.nuxtand.outputdirectories - Verify all dependencies are installed
- Check TypeScript errors
Hydration mismatches:
- Ensure server and client render the same HTML
- Avoid using
Date.now()or random values in templates - Check for conditional rendering based on client-only state