diff --git a/.opencode/skills/nuxt-website/SKILL.md b/.opencode/skills/nuxt-website/SKILL.md new file mode 100644 index 0000000..4c4573b --- /dev/null +++ b/.opencode/skills/nuxt-website/SKILL.md @@ -0,0 +1,990 @@ +--- +name: nuxt-website +description: Build and maintain Vue 3/Nuxt 4 marketing websites. Use when working with website pages, components, layouts, SEO, Nuxt configuration, or when user mentions 'website', 'marketing site', 'landing page', 'docs site', Vue, or Nuxt. +--- + +## 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 ` + + + + +``` + +### Component Naming + +- Use PascalCase for component files: `HeroSection.vue`, `FeatureCard.vue` +- Auto-import uses PascalCase in templates: ``, `` +- Organize by type: `components/ui/`, `components/layout/`, `components/content/` + +### Component Best Practices + +1. **Props**: Use TypeScript interfaces for type safety +2. **Emits**: Define emits explicitly with TypeScript +3. **Slots**: Use named slots for flexibility +4. **Composables**: Extract reusable logic to composables +5. **Auto-imports**: All components in `components/` are auto-imported +6. **Scoped styles**: Use ` +``` + +### Using Layouts in Pages + +```vue + + + + +``` + +## Nuxt Configuration + +### nuxt.config.ts + +```typescript +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: + +```typescript +// composables/useApi.ts +export const useApi = () => { + const config = useRuntimeConfig() + const baseUrl = config.public.apiBase + + const fetchData = async (endpoint: string): Promise => { + const { data, error } = await useFetch(`${baseUrl}${endpoint}`) + + if (error.value) { + throw new Error(`API Error: ${error.value.message}`) + } + + return data.value as T + } + + return { + fetchData + } +} +``` + +Usage in components: + +```vue + +``` + +### Common Composables + +- `useRoute()` - Access current route +- `useRouter()` - Navigate programmatically +- `useFetch()` - Data fetching with SSR support +- `useAsyncData()` - Advanced data fetching +- `useState()` - Shared state across components +- `useHead()` - Manage head tags +- `useSeoMeta()` - Manage SEO meta tags +- `useRuntimeConfig()` - Access runtime config + +## Data Fetching + +### useFetch + +```vue + +``` + +### useAsyncData + +For custom async operations: + +```vue + +``` + +## Styling + +### Global Styles + +```css +/* 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 + +```vue + +``` + +### CSS Modules + +```vue + + + +``` + +## Performance Optimization + +### Image Optimization + +Use `@nuxt/image` module: + +```vue + +``` + +### Lazy Loading Components + +```vue + + + +``` + +Or use Nuxt's `Lazy` prefix: + +```vue + +``` + +### Code Splitting + +```typescript +// 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 + +```typescript +// middleware/auth.ts +export default defineNuxtRouteMiddleware((to, from) => { + const user = useState('user') + + if (!user.value && to.path !== '/login') { + return navigateTo('/login') + } +}) +``` + +Usage: + +```vue + +``` + +### Global Middleware + +```typescript +// middleware/analytics.global.ts +export default defineNuxtRouteMiddleware((to, from) => { + // Runs on every route change + console.log('Navigating to:', to.path) +}) +``` + +## Plugins + +### Creating Plugins + +```typescript +// 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: + +```vue + +``` + +## API Routes + +### Creating API Endpoints + +```typescript +// 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 + +```bash +# 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 + +```bash +# .env +API_SECRET=secret123 +API_BASE_URL=https://api.example.com +SITE_URL=https://example.com +``` + +Access in Nuxt: + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + runtimeConfig: { + apiSecret: process.env.API_SECRET, + public: { + apiBase: process.env.API_BASE_URL + } + } +}) +``` + +### Vercel Deployment + +```json +// vercel.json +{ + "buildCommand": "npm run build", + "devCommand": "npm run dev", + "installCommand": "npm install", + "framework": "nuxtjs" +} +``` + +### Netlify Deployment + +```toml +# netlify.toml +[build] + command = "npm run build" + publish = ".output/public" + +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 +``` + +### Docker Deployment + +```dockerfile +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 + +```vue + + + +``` + +### Form Handling + +```vue + + +