Introduction
Building modern frontend applications with Vue.js and Nuxt requires more than just knowing the syntax—it demands a structured approach to component design, state management, and routing. Whether you're a junior developer or a seasoned architect, having a set of proven, ready-to-use prompts can accelerate your development workflow and ensure best practices. This collection covers 10 essential prompts for Vue 3 (Composition API) and Nuxt 3, including component patterns, composables, route guards, Pinia stores, and testing. Each prompt is a copy-paste ready template you can adapt to your project.
1. Generic Reusable Component with v-model
Task: Create a reusable input component that supports two-way binding via v-model (modelValue / update:modelValue).
Prompt:
<template>
<div class="custom-input">
<label v-if="label" :for="id">{{ label }}</label>
<input
:id="id"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
:class="{ 'error': error }"
/>
<span v-if="error" class="error-message">{{ error }}</span>
</div>
</template>
<script setup>
defineProps({
modelValue: String,
label: String,
id: String,
error: String
})
defineEmits(['update:modelValue'])
</script>
Usage: <CustomInput v-model="name" label="Name" :error="nameError" />
2. Composable for Fetching Data with Loading/Error States
Task: Create a composable useFetchData that handles loading, error, and data states using Vue's ref and watchEffect.
Prompt:
// composables/useFetchData.js
import { ref, watchEffect } from 'vue'
export function useFetchData(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
if (!response.ok) throw new Error('Network error')
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
watchEffect(() => {
fetchData()
})
return { data, error, loading, fetchData }
}
Usage: const { data, loading, error } = useFetchData('/api/users')
3. Nuxt 3 Route Middleware for Auth Guard
Task: Protect a route from unauthenticated users using Nuxt's defineNuxtRouteMiddleware.
Prompt:
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
const token = useCookie('auth_token')
if (!token.value && to.path !== '/login') {
return navigateTo('/login')
}
})
Usage: Place in middleware/ folder and it runs globally. To apply only to specific pages, use definePageMeta({ middleware: 'auth' }).
4. Pinia Store for User Authentication
Task: Build a Pinia auth store with login, logout, and token management.
Prompt:
// stores/auth.js
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
token: null
}),
getters: {
isAuthenticated: (state) => !!state.token
},
actions: {
async login(credentials) {
const { data, error } = await useFetch('/api/login', {
method: 'POST',
body: credentials
})
if (error.value) throw new Error(error.value.message)
this.token = data.value.token
this.user = data.value.user
useCookie('auth_token').value = this.token
},
logout() {
this.token = null
this.user = null
useCookie('auth_token').value = null
}
}
})
Usage: const auth = useAuthStore(); await auth.login({ email, password })
5. Reusable Modal Component with Teleport
Task: Create a modal that uses Vue's <Teleport> to render above the app root, with open/close logic.
Prompt:
<template>
<Teleport to="body">
<div v-if="visible" class="modal-overlay" @click.self="close">
<div class="modal-content">
<button @click="close" class="close-btn">×</button>
<slot />
</div>
</div>
</Teleport>
</template>
<script setup>
defineProps({ visible: Boolean })
const emit = defineEmits(['close'])
const close = () => emit('close')
</script>
Usage: <Modal :visible="showModal" @close="showModal = false"><p>Content</p></Modal>
6. Dynamic Table Component with Sorting
Task: Build a generic table that accepts columns config and data, with click-to-sort headers.
Prompt:
<template>
<table>
<thead>
<tr>
<th v-for="col in columns" :key="col.key" @click="sortBy(col.key)">
{{ col.label }}
<span v-if="sortKey === col.key">{{ sortOrder === 'asc' ? '▲' : '▼' }}</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in sortedData" :key="row.id">
<td v-for="col in columns" :key="col.key">{{ row[col.key] }}</td>
</tr>
</tbody>
</table>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
columns: Array,
data: Array
})
const sortKey = ref('')
const sortOrder = ref('asc')
const sortBy = (key) => {
if (sortKey.value === key) {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
} else {
sortKey.value = key
sortOrder.value = 'asc'
}
}
const sortedData = computed(() => {
if (!sortKey.value) return props.data
return [...props.data].sort((a, b) => {
const valA = a[sortKey.value]
const valB = b[sortKey.value]
if (valA < valB) return sortOrder.value === 'asc' ? -1 : 1
if (valA > valB) return sortOrder.value === 'asc' ? 1 : -1
return 0
})
})
</script>
Usage: <DataTable :columns="[{ key: 'name', label: 'Name' }, { key: 'age', label: 'Age' }]" :data="users" />
7. Composable for Debounced Input
Task: Create a composable that returns a debounced value after the user stops typing.
Prompt:
// composables/useDebounce.js
import { ref, watch } from 'vue'
export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value)
let timeout = null
watch(value, (newVal) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
debouncedValue.value = newVal
}, delay)
})
return debouncedValue
}
Usage: const search = ref(''); const debouncedSearch = useDebounce(search, 500)
8. Nuxt 3 Server API Route
Task: Create a simple API endpoint in Nuxt that returns JSON data.
Prompt:
// server/api/users.js
export default defineEventHandler((event) => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
return { users }
})
Usage: Access via /api/users in your Nuxt app. Use useFetch('/api/users') on the client.
9. Vue Router Navigation Guard (beforeEach)
Task: Add a global navigation guard that logs route changes and checks for a meta property.
Prompt:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: () => import('../pages/Home.vue') },
{ path: '/admin', component: () => import('../pages/Admin.vue'), meta: { requiresAuth: true } }
]
})
router.beforeEach((to, from) => {
console.log(`Navigating from ${from.path} to ${to.path}`)
if (to.meta.requiresAuth && !localStorage.getItem('token')) {
return { path: '/login' }
}
})
export default router
Usage: Import and use app.use(router) in main.js.
10. Pinia Store with Persisted State (localStorage)
Task: Extend a Pinia store to automatically save and restore state from localStorage.
Prompt:
// stores/settings.js
import { defineStore } from 'pinia'
export const useSettingsStore = defineStore('settings', {
state: () => ({
theme: 'light',
language: 'en'
}),
actions: {
loadFromStorage() {
const saved = localStorage.getItem('settings')
if (saved) {
const parsed = JSON.parse(saved)
this.theme = parsed.theme || 'light'
this.language = parsed.language || 'en'
}
},
saveToStorage() {
localStorage.setItem('settings', JSON.stringify({
theme: this.theme,
language: this.language
}))
}
}
})
// Call loadFromStorage on app init, and watch for changes to auto-save
Usage: In a component: const settings = useSettingsStore(); settings.loadFromStorage(); Then call settings.saveToStorage() after changes.
Conclusion
These 10 prompts cover the most common patterns in Vue.js and Nuxt development—from generic components and composables to authentication, routing, and state persistence. By using these templates, you can save hours of boilerplate coding and focus on your app's unique logic. For further reading, check the official Vue documentation (vuejs.org) and Nuxt documentation (nuxt.com), which provide exhaustive guides on Composition API, Pinia, and routing. Start integrating these prompts into your next project and see your productivity soar.
Comments