10 Prompts for Vue.js and Nuxt: From Components to Pinia Stores

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">&times;</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.

← All posts

Comments