Introduction
Building modern Vue.js and Nuxt applications requires more than just knowing the framework—it demands efficient workflows. As a developer who uses AI daily to speed up tasks, I've compiled a collection of battle-tested prompts that solve real problems: from generating reusable components and composables to handling complex routing and state management with Pinia. These prompts are designed to be copied, adapted, and used immediately in your IDE or chat interface. Each prompt includes a concrete use case, the exact prompt text, and the expected output. Let's dive into 12 prompts that will transform how you work with Vue.js and Nuxt.
1. Generate a Reusable Vue Component with Props Validation
Use case: You need a button component with multiple variants (primary, secondary, outline) and proper prop validation. Instead of writing boilerplate, use this prompt to get a production-ready component.
Prompt:
Create a Vue 3 Button component with the following requirements:
- Props: variant (primary, secondary, outline), size (sm, md, lg), disabled (boolean)
- Emit a 'click' event
- Use scoped CSS for styling
- Include TypeScript prop validation
- Add a default slot for children
Expected output: A <BaseButton> component with typed props, event emission, and responsive styling. Example:
<template>
<button
:class="['btn', `btn--${variant}`, `btn--${size}`, { 'btn--disabled': disabled }]"
:disabled="disabled"
@click="$emit('click', $event)"
>
<slot />
</button>
</template>
<script setup lang="ts">
withDefaults(defineProps<{
variant?: 'primary'
| 'secondary' | 'outline'
size?: 'sm'
| 'md' | 'lg'
disabled?: boolean
}>(), {
variant: 'primary',
size: 'md',
disabled: false
})
defineEmits<{
click: [event: MouseEvent]
}>()
</script>
<style scoped>
.btn { padding: 0.5rem 1rem; border-radius: 4px; }
.btn--primary { background: #42b883; color: white; }
.btn--disabled { opacity: 0.5; pointer-events: none; }
</style>
Why it works: This prompt specifies clear requirements (props, events, slots) and asks for TypeScript support, which aligns with Vue 3 best practices. The AI understands component architecture patterns.
2. Create a Composable for API Data Fetching
Use case: You need a composable that fetches data from an API, handles loading and error states, and supports reactive updates. This is a common pattern in Nuxt apps.
Prompt:
Write a Vue 3 composable called 'useApiFetch' that:
- Accepts a URL string and optional options object (method, headers, body)
- Returns reactive refs: data, error, loading
- Uses the fetch API with async/await
- Automatically aborts previous requests when called again
- Provides a 'refresh' function to re-fetch manually
Expected output: A reusable composable that can be used in any component:
import { ref } from 'vue'
export function useApiFetch<T = unknown>(url: string, options?: RequestInit) {
const data = ref<T | null>(null)
const error = ref<string | null>(null)
const loading = ref(false)
let abortController: AbortController | null = null
async function execute() {
abortController?.abort()
abortController = new AbortController()
loading.value = true
error.value = null
try {
const response = await fetch(url, { ...options, signal: abortController.signal })
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = await response.json()
} catch (e) {
if (e instanceof Error && e.name !== 'AbortError') {
error.value = e.message
}
} finally {
loading.value = false
}
}
execute()
return { data, error, loading, refresh: execute }
}
Why it works: The prompt explicitly mentions aborting previous requests—a real-world concern that prevents race conditions. The AI generates a complete composable with error handling.
3. Generate a Pinia Store for User Authentication
Use case: You need a Pinia store that manages user authentication state, including login, logout, and token persistence. This is a core requirement for most apps.
Prompt:
Create a Pinia store for authentication with:
- State: user (object or null), token (string or null)
- Actions: login(email, password), logout(), checkAuth()
- Use localStorage for token persistence
- Include a getter 'isAuthenticated' that returns boolean
- Mock the API call in login (simulate with setTimeout)
Expected output: A complete store with persistent state:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref<{ email: string; name: string } | null>(null)
const token = ref<string | null>(localStorage.getItem('auth_token'))
const isAuthenticated = computed(() => !!token.value)
async function login(email: string, password: string) {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000))
if (email === 'test@example.com' && password === 'password') {
const mockToken = 'mock-jwt-token-' + Date.now()
token.value = mockToken
user.value = { email, name: 'Test User' }
localStorage.setItem('auth_token', mockToken)
} else {
throw new Error('Invalid credentials')
}
}
function logout() {
user.value = null
token.value = null
localStorage.removeItem('auth_token')
}
function checkAuth() {
if (token.value) {
// Optionally validate token with backend
user.value = { email: 'user@example.com', name: 'User' }
}
}
return { user, token, isAuthenticated, login, logout, checkAuth }
})
Why it works: The prompt covers state, actions, getters, and persistence—all essential for a real auth store. The mock API call makes it testable immediately.
4. Create a Nuxt Layout with Dynamic Navigation
Use case: You need a default layout for a Nuxt app that includes a navigation bar with dynamic links based on authentication state.
Prompt:
Create a Nuxt 3 default layout with:
- A responsive navigation bar using Tailwind CSS classes
- Dynamic links: 'Home', 'About' for all users, 'Dashboard' for authenticated users
- Use the useAuthStore composable to check authentication
- Include a slot for page content
- Add a mobile hamburger menu toggle
Expected output: A layout file layouts/default.vue:
<template>
<div>
<nav class="bg-gray-800 text-white p-4">
<div class="container mx-auto flex items-center justify-between">
<div class="flex space-x-4">
<NuxtLink to="/" class="hover:text-gray-300">Home</NuxtLink>
<NuxtLink to="/about" class="hover:text-gray-300">About</NuxtLink>
<NuxtLink v-if="auth.isAuthenticated" to="/dashboard" class="hover:text-gray-300">Dashboard</NuxtLink>
</div>
<button @click="menuOpen = !menuOpen" class="md:hidden">☰</button>
</div>
<div v-if="menuOpen" class="md:hidden mt-2 space-y-2">
<NuxtLink to="/" class="block px-4 py-2 hover:bg-gray-700">Home</NuxtLink>
<NuxtLink to="/about" class="block px-4 py-2 hover:bg-gray-700">About</NuxtLink>
<NuxtLink v-if="auth.isAuthenticated" to="/dashboard" class="block px-4 py-2 hover:bg-gray-700">Dashboard</NuxtLink>
</div>
</nav>
<main class="container mx-auto p-4">
<slot />
</main>
</div>
</template>
<script setup lang="ts">
const auth = useAuthStore()
const menuOpen = ref(false)
</script>
Why it works: This prompt integrates Nuxt-specific features (NuxtLink, layout, useAuthStore) with real-world UI requirements like responsive design and conditional rendering.
5. Write a Vue Router Configuration with Route Guards
Use case: You need a router setup with lazy-loaded routes and navigation guards that redirect unauthenticated users to login.
Prompt:
Create a Vue Router configuration for a Vue 3 app with:
- Lazy-loaded routes: Home, About, Dashboard (requires auth), Login
- A beforeEach navigation guard that checks if the route requires auth
- If not authenticated, redirect to /login with a redirect query parameter
- After login, redirect back to the original route
- Use createRouter with history mode
Expected output: A router file router/index.ts:
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const routes = [
{ path: '/', name: 'Home', component: () => import('@/views/Home.vue') },
{ path: '/about', name: 'About', component: () => import('@/views/About.vue') },
{ path: '/login', name: 'Login', component: () => import('@/views/Login.vue') },
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
const auth = useAuthStore()
if (to.meta.requiresAuth && !auth.isAuthenticated) {
next({ name: 'Login', query: { redirect: to.fullPath } })
} else {
next()
}
})
export default router
Why it works: The prompt covers lazy loading, meta fields, and redirect logic—all common patterns. The AI generates a complete, working router setup.
6. Generate a Vue 3 Form Component with Validation
Use case: You need a login form with email and password fields, client-side validation, and error display. This is a frequent requirement.
Prompt:
Create a Vue 3 login form component with:
- Reactive form state using refs
- Validation rules: email must be valid format, password minimum 6 characters
- Display inline error messages for each field
- Submit handler that calls the auth store's login action
- Show loading state during submission
- Disable submit button when form is invalid or loading
Expected output: A <LoginForm> component with full validation:
<template>
<form @submit.prevent="handleSubmit" class="max-w-md mx-auto p-4 space-y-4">
<div>
<label for="email">Email</label>
<input v-model="email" type="email" id="email" class="border p-2 w-full" />
<p v-if="emailError" class="text-red-500 text-sm">{{ emailError }}</p>
</div>
<div>
<label for="password">Password</label>
<input v-model="password" type="password" id="password" class="border p-2 w-full" />
<p v-if="passwordError" class="text-red-500 text-sm">{{ passwordError }}</p>
</div>
<button type="submit" :disabled="!isValid || loading" class="bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50">
{{ loading ? 'Logging in...' : 'Login' }}
</button>
</form>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useAuthStore } from '@/stores/auth'
const auth = useAuthStore()
const email = ref('')
const password = ref('')
const loading = ref(false)
const emailError = computed(() => {
if (!email.value) return 'Email is required'
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) return 'Invalid email format'
return ''
})
const passwordError = computed(() => {
if (!password.value) return 'Password is required'
if (password.value.length < 6) return 'Password must be at least 6 characters'
return ''
})
const isValid = computed(() => !emailError.value && !passwordError.value && email.value && password.value)
async function handleSubmit() {
if (!isValid.value) return
loading.value = true
try {
await auth.login(email.value, password.value)
} catch (e) {
alert('Login failed')
} finally {
loading.value = false
}
}
</script>
Why it works: The prompt combines form state, validation, async submission, and loading states—all in one component. The AI handles the reactive logic correctly.
7. Create a Nuxt Server API Endpoint with Validation
Use case: You need a Nuxt server route that handles POST requests for user registration, with input validation using Zod.
Prompt:
Create a Nuxt 3 server API route at /api/register that:
- Accepts POST requests with JSON body: email, password, name
- Validates input using Zod (email format, password min 6 chars, name min 2 chars)
- Returns 400 with error messages if validation fails
- Returns 201 with success message if valid
- Simulate saving to a database (just log the data)
Expected output: A server route file server/api/register.post.ts:
import { z } from 'zod'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(6, 'Password must be at least 6 characters'),
name: z.string().min(2, 'Name must be at least 2 characters')
})
const result = schema.safeParse(body)
if (!result.success) {
throw createError({
statusCode: 400,
statusMessage: result.error.errors.map(e => e.message).join(', ')
})
}
// Simulate database save
console.log('User registered:', result.data)
return { success: true, message: 'User registered successfully' }
})
Why it works: This prompt leverages Nuxt's file-based routing for API endpoints and Zod for validation—a modern, type-safe approach.
8. Generate a Composable for Debounced Search
Use case: You need a composable that debounces user input before making an API call, useful for search-as-you-type functionality.
Prompt:
Write a Vue 3 composable called 'useDebouncedSearch' that:
- Accepts a search function that returns a promise
- Returns reactive refs: query (string), results (array), loading (boolean)
- Debounces the search by 300ms using setTimeout
- Cancels previous pending search requests
- Automatically triggers search when query changes
Expected output: A composable for efficient search:
import { ref, watch } from 'vue'
export function useDebouncedSearch<T>(
searchFn: (query: string) => Promise<T[]>,
delay = 300
) {
const query = ref('')
const results = ref<T[]>([])
const loading = ref(false)
let timer: ReturnType<typeof setTimeout> | null = null
let abortController: AbortController | null = null
watch(query, (newQuery) => {
if (timer) clearTimeout(timer)
timer = setTimeout(async () => {
if (!newQuery.trim()) {
results.value = []
return
}
loading.value = true
abortController?.abort()
abortController = new AbortController()
try {
const data = await searchFn(newQuery)
results.value = data
} catch {
// Handle error silently
} finally {
loading.value = false
}
}, delay)
})
return { query, results, loading }
}
Why it works: The prompt addresses a real performance concern—debouncing—and includes cancellation logic to avoid race conditions.
9. Create a Vue 3 Directive for Click Outside
Use case: You need a custom directive that detects clicks outside an element, useful for closing dropdowns or modals.
Prompt:
Create a Vue 3 custom directive called 'v-click-outside' that:
- Accepts a callback function as value
- Attaches a click event listener to the document
- Detects if the click target is outside the element
- Calls the callback when outside click is detected
- Cleans up the event listener on unmount
- Works with the composition API style
Expected output: A directive file directives/clickOutside.ts:
import { Directive, DirectiveBinding } from 'vue'
export const vClickOutside: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const handler = (event: MouseEvent) => {
if (!el.contains(event.target as Node)) {
binding.value(event)
}
}
document.addEventListener('click', handler)
el._clickOutsideHandler = handler
},
unmounted(el: HTMLElement) {
document.removeEventListener('click', el._clickOutsideHandler)
}
}
Why it works: This prompt covers lifecycle hooks (mounted, unmounted) and event handling—a common pattern for custom directives.
10. Generate a Nuxt Middleware for Route Protection
Use case: You need a Nuxt middleware that redirects unauthenticated users to the login page, but also handles the redirect back after login.
Prompt:
Create a Nuxt 3 route middleware called 'auth' that:
- Checks if the user is authenticated via useAuthStore
- If not authenticated, redirects to /login with a query parameter ?redirect=currentPath
- If authenticated, allows navigation
- Runs on the client side only (use nuxtApp.ssrContext check)
Expected output: A middleware file middleware/auth.ts:
export default defineNuxtRouteMiddleware((to, from) => {
const auth = useAuthStore()
// Only run on client side
if (process.client && !auth.isAuthenticated) {
return navigateTo({
path: '/login',
query: { redirect: to.fullPath }
})
}
})
Why it works: This prompt uses Nuxt-specific APIs (defineNuxtRouteMiddleware, navigateTo) and handles SSR gracefully.
11. Create a Composable for Local Storage with Reactivity
Use case: You need a composable that syncs reactive state with localStorage, so data persists across page reloads.
Prompt:
Write a Vue 3 composable called 'useLocalStorage' that:
- Accepts a key (string) and default value
- Returns a reactive ref that syncs with localStorage
- Watches for changes and updates localStorage automatically
- Handles JSON serialization/deserialization
- Handles errors gracefully (e.g., localStorage full)
Expected output: A composable for persistent reactive state:
import { ref, watch } from 'vue'
export function useLocalStorage<T>(key: string, defaultValue: T) {
let initialValue: T
try {
const stored = localStorage.getItem(key)
initialValue = stored ? JSON.parse(stored) : defaultValue
} catch {
initialValue = defaultValue
}
const data = ref<T>(initialValue)
watch(data, (newValue) => {
try {
localStorage.setItem(key, JSON.stringify(newValue))
} catch (e) {
console.error('Failed to save to localStorage', e)
}
}, { deep: true })
return data
}
Why it works: The prompt covers serialization, error handling, and deep watching—practical concerns when dealing with localStorage.
12. Generate a Vue 3 Transition Wrapper Component
Use case: You need a reusable component that wraps content with a fade transition, useful for toggling visibility.
Prompt:
Create a Vue 3 component called 'FadeTransition' that:
- Uses Vue's built-in <Transition> component
- Provides a fade animation (opacity 0 to 1) over 300ms
- Accepts a 'show' prop to control visibility
- Has a default slot for content
- Supports custom duration via prop
Expected output: A component with smooth transitions:
<template>
<Transition name="fade">
<div v-if="show">
<slot />
</div>
</Transition>
</template>
<script setup lang="ts">
withDefaults(defineProps<{
show: boolean
duration?: number
}>(), {
duration: 300
})
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity v-bind(duration + 'ms') ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
Why it works: This prompt combines Vue's Transition component with dynamic CSS via v-bind—a powerful pattern for reusable animations.
Conclusion
These 12 prompts cover the most common development tasks in Vue.js and Nuxt: from component generation to state management, routing, and custom composables. Each prompt is designed to be pasted directly into your AI tool and produce production-ready code. The key to effective prompting is specificity—include requirements for props, types, lifecycle hooks, and error handling. By using these prompts, you can reduce boilerplate writing by hours and focus on the unique logic of your application. Start using them today and see your productivity soar.
Comments