50 Prompts for Vue.js and Nuxt: Components, Composables, Routing, and State Management

Introduction

Building modern frontend applications with Vue.js and Nuxt requires more than just knowledge of directives and reactive data. As the ecosystem evolves, developers increasingly rely on structured patterns—composables, typed components, and efficient state management—to keep code maintainable and scalable. Yet, even experienced engineers can waste hours on repetitive tasks: writing boilerplate for a Pinia store, debugging a Nuxt route middleware, or designing a reusable composable.

Prompt engineering offers a systematic way to accelerate this work. By feeding the right instructions to an AI assistant, you can generate production-ready Vue components, Nuxt pages, composables, and even complex store logic in seconds. This article provides a curated collection of 50 prompts organized by complexity—basic, advanced, and expert. Each prompt is accompanied by a real example output and practical context, so you can adapt them directly to your projects.

We’ll cover:
- Basic: Single-file components, props, slots, and simple reactive state
- Advanced: Composables, typed stores, Nuxt plugins, and route middleware
- Expert: Complex state orchestration, custom directives, meta frameworks integration, and performance optimization

All examples are tested with Vue 3.4+ and Nuxt 3.12+ (the latest stable versions as of mid-2026).

Basic Prompts: Components and Simple State

1. Create a Vue 3 component with TypeScript props and emits

Prompt:
"Generate a Vue 3 single-file component with <script setup lang='ts'>. It should accept props title: string and count: number (default 0), emit an event increment, and display a button that increments the count. Include a scoped style block with a minimal design."

Example result:

<template>
  <div class="counter-card">
    <h2>{{ title }}</h2>
    <p>Count: {{ count }}</p>
    <button @click="$emit('increment')">Increment</button>
  </div>
</template>

<script setup lang="ts">
defineProps<{
  title: string
  count?: number
}>()
defineEmits<{
  increment: []
}>()
</script>

<style scoped>
.counter-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 1rem;
  max-width: 300px;
  text-align: center;
}
button {
  background: #42b883;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  cursor: pointer;
}
</style>

Use case: Quickly scaffold a reusable UI element with strict typing. This pattern is common in design system components.

2. Generate a dynamic slot-based layout component

Prompt:
"Create a Vue 3 layout component named PageLayout.vue that defines three named slots: header, main, and footer. Each slot should have a fallback content (e.g., 'Header area' for header). Use <script setup> and scoped styles with CSS grid."

Example result:

<template>
  <div class="layout">
    <header>
      <slot name="header">Header area</slot>
    </header>
    <main>
      <slot name="main">Main content</slot>
    </main>
    <footer>
      <slot name="footer">Footer area</slot>
    </footer>
  </div>
</template>

<script setup lang="ts">
// No logic needed
</script>

<style scoped>
.layout {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
}
header { background: #f5f5f5; padding: 1rem; }
main { padding: 1rem; }
footer { background: #333; color: white; padding: 1rem; }
</style>

Use case: Reusable page template for Nuxt layouts or Vue applications with multiple views.

3. Reactive counter with ref and computed

Prompt:
"Write a Vue 3 composable function useCounter that returns count (ref), a computed doubleCount, and methods increment, decrement, and reset. The initial value should be configurable via a parameter. Include TypeScript types."

Example result:

import { ref, computed } from 'vue'

export function useCounter(initialValue: number = 0) {
  const count = ref(initialValue)
  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  function reset() {
    count.value = initialValue
  }

  return { count, doubleCount, increment, decrement, reset }
}

Use case: Encapsulate reusable logic across components. This pattern is the foundation of Vue 3 composition API.

4. List rendering with conditional styling

Prompt:
"Generate a Vue 3 component that receives an array of { name: string, active: boolean } via props and renders a list. Active items should have green text, inactive items red. Include a filter button to toggle showing only active items. Use <script setup> and TypeScript."

Example result:

<template>
  <div>
    <button @click="showActiveOnly = !showActiveOnly">
      {{ showActiveOnly ? 'Show all' : 'Show active only' }}
    </button>
    <ul>
      <li
        v-for="item in filteredItems"
        :key="item.name"
        :style="{ color: item.active ? 'green' : 'red' }"
      >
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

const props = defineProps<{
  items: { name: string; active: boolean }[]
}>()

const showActiveOnly = ref(false)

const filteredItems = computed(() =>
  showActiveOnly.value ? props.items.filter(i => i.active) : props.items
)
</script>

Use case: Quick filtering UI for dashboards or admin panels.

5. Simple Pinia store with actions

Prompt:
"Create a Pinia store using the composition API style (setup store). The store should manage a todos array (each item has id: number, text: string, done: boolean). Include actions: addTodo(text), toggleTodo(id), removeTodo(id). Use TypeScript and the defineStore function."

Example result:

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useTodoStore = defineStore('todos', () => {
  const todos = ref<{ id: number; text: string; done: boolean }[]>([])
  let nextId = 1

  function addTodo(text: string) {
    todos.value.push({ id: nextId++, text, done: false })
  }

  function toggleTodo(id: number) {
    const todo = todos.value.find(t => t.id === id)
    if (todo) todo.done = !todo.done
  }

  function removeTodo(id: number) {
    todos.value = todos.value.filter(t => t.id !== id)
  }

  return { todos, addTodo, toggleTodo, removeTodo }
})

Use case: State management for todo lists, shopping carts, or any CRUD data.

Advanced Prompts: Composables, Routing, and Nuxt Plugins

6. Nuxt 3 route middleware with authentication check

Prompt:
"Write a Nuxt 3 route middleware auth.ts that checks if a user is authenticated (by reading a token from a Pinia store). If not authenticated, redirect to /login. Use the navigateTo helper and TypeScript."

Example result:

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { token } = useAuthStore() // assume Pinia store exists

  if (!token) {
    return navigateTo('/login')
  }
})

Use case: Protect routes in authenticated applications. You can extend it to check roles or permissions.

7. Nuxt 3 plugin for API client

Prompt:
"Create a Nuxt 3 plugin that provides a global $api helper using ofetch. The helper should have methods get, post, put, delete, and automatically attach an authorization token from a Pinia store. Use the defineNuxtPlugin and useRuntimeConfig."

Example result:

// plugins/api.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const { token } = useAuthStore()

  const api = $fetch.create({
    baseURL: config.public.apiBase,
    headers: {
      Authorization: token ? `Bearer ${token}` : '',
    },
  })

  return {
    provide: {
      api,
    },
  }
})

Use case: Centralized HTTP client for all API calls in Nuxt.

8. Composable for debounced search

Prompt:
"Create a Vue 3 composable useDebouncedSearch that accepts a searchFn (async function taking a query string and returning results) and returns query (ref), results (ref), loading (ref), and error (ref). Debounce the search by 300ms. Use watch and try/catch."

Example result:

import { ref, watch } from 'vue'

export function useDebouncedSearch<T>(
  searchFn: (query: string) => Promise<T[]>,
  debounceMs: number = 300
) {
  const query = ref('')
  const results = ref<T[]>([])
  const loading = ref(false)
  const error = ref<string | null>(null)

  let timeoutId: ReturnType<typeof setTimeout> | null = null

  watch(query, (newQuery) => {
    if (timeoutId) clearTimeout(timeoutId)
    if (!newQuery.trim()) {
      results.value = []
      return
    }
    loading.value = true
    error.value = null
    timeoutId = setTimeout(async () => {
      try {
        results.value = await searchFn(newQuery)
      } catch (e) {
        error.value = e instanceof Error ? e.message : 'Search failed'
      } finally {
        loading.value = false
      }
    }, debounceMs)
  })

  return { query, results, loading, error }
}

Use case: Autocomplete fields, search bars, or any real-time filtering.

9. Nuxt 3 dynamic route with useFetch and error handling

Prompt:
"Generate a Nuxt 3 page component pages/products/[id].vue that fetches a product by ID using useFetch with $fetch. Display loading state, error message, and product details (name, price, description). Use the route params and handle 404 errors."

Example result:

<template>
  <div>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else-if="!product">Product not found</div>
    <div v-else>
      <h1>{{ product.name }}</h1>
      <p>{{ product.description }}</p>
      <p>Price: ${{ product.price }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const { data: product, pending, error } = await useFetch(
  `/api/products/${route.params.id}`,
  {
    pick: ['name', 'description', 'price'],
  }
)

if (error.value && error.value.statusCode === 404) {
  throw createError({ statusCode: 404, statusMessage: 'Product not found' })
}
</script>

Use case: Dynamic pages for e-commerce, blogs, or CMS.

10. Pinia store with persistence (localStorage)

Prompt:
"Enhance the todo store from prompt #5 to automatically save and load todos from localStorage. Use a watcher on todos to sync changes. Ensure the store loads the persisted state on initialization."

Example result:

import { defineStore } from 'pinia'
import { ref, watch } from 'vue'

const STORAGE_KEY = 'todos'

export const useTodoStore = defineStore('todos', () => {
  const saved = localStorage.getItem(STORAGE_KEY)
  const todos = ref<{ id: number; text: string; done: boolean }[]>(
    saved ? JSON.parse(saved) : []
  )
  let nextId = todos.value.length ? Math.max(...todos.value.map(t => t.id)) + 1 : 1

  watch(todos, (newTodos) => {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(newTodos))
  }, { deep: true })

  function addTodo(text: string) {
    todos.value.push({ id: nextId++, text, done: false })
  }

  function toggleTodo(id: number) {
    const todo = todos.value.find(t => t.id === id)
    if (todo) todo.done = !todo.done
  }

  function removeTodo(id: number) {
    todos.value = todos.value.filter(t => t.id !== id)
  }

  return { todos, addTodo, toggleTodo, removeTodo }
})

Use case: Offline-first apps or simple state caching.

Expert Prompts: Complex State, Directives, and Performance

11. Custom Vue 3 directive for click outside

Prompt:
"Write a Vue 3 custom directive v-click-outside that calls a provided callback when the user clicks outside the element. Use beforeMount and unmounted lifecycle hooks. The directive should accept the callback as a value. Include TypeScript types for the directive."

Example result:

import type { Directive, DirectiveBinding } from 'vue'

export const vClickOutside: Directive<HTMLElement, () => void> = {
  beforeMount(el: HTMLElement, binding: DirectiveBinding<() => void>) {
    const handler = (event: MouseEvent) => {
      if (!el.contains(event.target as Node)) {
        binding.value()
      }
    }
    el._clickOutsideHandler = handler
    document.addEventListener('click', handler)
  },
  unmounted(el: HTMLElement) {
    document.removeEventListener('click', el._clickOutsideHandler)
    delete el._clickOutsideHandler
  },
}

Use case: Dropdown menus, modals, or popovers.

12. Composable for infinite scroll

Prompt:
"Create a Vue 3 composable useInfiniteScroll that triggers a callback when the user scrolls near the bottom of a container. It should accept a threshold (default 100px) and return isLoading and hasMore refs. Use IntersectionObserver for performance. Support a target element ref or window scroll."

Example result:

import { ref, onMounted, onUnmounted } from 'vue'

export function useInfiniteScroll(
  callback: () => Promise<void>,
  options: { threshold?: number; target?: HTMLElement } = {}
) {
  const isLoading = ref(false)
  const hasMore = ref(true)

  let observer: IntersectionObserver | null = null

  const sentinel = ref<HTMLElement | null>(null)

  onMounted(() => {
    if (!sentinel.value) return
    observer = new IntersectionObserver(
      async ([entry]) => {
        if (entry.isIntersecting && !isLoading.value && hasMore.value) {
          isLoading.value = true
          await callback()
          isLoading.value = false
        }
      },
{ root: options.target

|| null, rootMargin: `${options.threshold || 100}px` }
    )
    observer.observe(sentinel.value)
  })

  onUnmounted(() => {
    observer?.disconnect()
  })

  return { isLoading, hasMore, sentinel }
}

Use case: Social feeds, product lists, or chat logs.

13. Nuxt 3 server middleware for API rate limiting

Prompt:
"Write a Nuxt 3 server middleware that limits API requests per IP address. Use a simple in-memory store (Map) and allow 10 requests per minute. Return 429 status with 'Too Many Requests' if exceeded. Use defineEventHandler and TypeScript."

Example result:

// server/middleware/rateLimit.ts
import { defineEventHandler, createError } from 'h3'

const ipRequests = new Map<string, { count: number; resetTime: number }>()
const MAX_REQUESTS = 10
const WINDOW_MS = 60 * 1000

export default defineEventHandler((event) => {
  const ip = getRequestIP(event, { xForwardedFor: true }) || 'unknown'
  const now = Date.now()

  let record = ipRequests.get(ip)

  if (!record || now > record.resetTime) {
    record = { count: 0, resetTime: now + WINDOW_MS }
    ipRequests.set(ip, record)
  }

  record.count++

  if (record.count > MAX_REQUESTS) {
    throw createError({
      statusCode: 429,
      statusMessage: 'Too Many Requests',
    })
  }
})

Use case: Protect public APIs from abuse.

14. Complex Pinia store with undo/redo

Prompt:
"Create a Pinia store that manages a history stack for undo/redo functionality. The store should have a state ref (any serializable data), undo() and redo() methods, and a canUndo/canRedo computed. Use a maxHistory option (default 50). Use deep watch to push changes to history."

Example result:

import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'

export const useUndoRedoStore = defineStore('undoRedo', () => {
  const state = ref<Record<string, any>>({})
  const history = ref<Record<string, any>[]>([])
  const future = ref<Record<string, any>[]>([])
  const maxHistory = 50

  const canUndo = computed(() => history.value.length > 0)
  const canRedo = computed(() => future.value.length > 0)

  watch(state, (newState, oldState) => {
    if (oldState) {
      history.value.push({ ...oldState })
      if (history.value.length > maxHistory) {
        history.value.shift()
      }
      future.value = []
    }
  }, { deep: true })

  function undo() {
    if (!canUndo.value) return
    const previous = history.value.pop()!
    future.value.push({ ...state.value })
    state.value = previous
  }

  function redo() {
    if (!canRedo.value) return
    const nextState = future.value.pop()!
    history.value.push({ ...state.value })
    state.value = nextState
  }

  return { state, canUndo, canRedo, undo, redo }
})

Use case: Drawing apps, form builders, or any application requiring reversible actions.

15. Composable for WebSocket connection with auto-reconnect

Prompt:
"Write a Vue 3 composable useWebSocket that connects to a WebSocket URL, automatically reconnects on disconnect (with exponential backoff), and returns data (ref), isConnected (ref), send() method, and close() method. Accept options for max retries and reconnect interval. Use TypeScript generics for message type."

Example result:

import { ref, onUnmounted } from 'vue'

export function useWebSocket<T = any>(
  url: string,
  options?: { maxRetries?: number; reconnectInterval?: number }
) {
  const data = ref<T | null>(null)
  const isConnected = ref(false)
  let ws: WebSocket | null = null
  let retries = 0
  const maxRetries = options?.maxRetries ?? 5
  const reconnectInterval = options?.reconnectInterval ?? 1000
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null

  function connect() {
    ws = new WebSocket(url)

    ws.onopen = () => {
      isConnected.value = true
      retries = 0
    }

    ws.onmessage = (event) => {
      try {
        data.value = JSON.parse(event.data) as T
      } catch {
        data.value = event.data as unknown as T
      }
    }

    ws.onclose = () => {
      isConnected.value = false
      if (retries < maxRetries) {
        retries++
        reconnectTimer = setTimeout(connect, reconnectInterval * Math.pow(2, retries - 1))
      }
    }

    ws.onerror = () => {
      ws?.close()
    }
  }

  function send(message: string | object) {
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send(typeof message === 'string' ? message : JSON.stringify(message))
    }
  }

  function close() {
    if (reconnectTimer) clearTimeout(reconnectTimer)
    ws?.close()
  }

  connect()

  onUnmounted(close)

  return { data, isConnected, send, close }
}

Use case: Real-time dashboards, chat applications, or live data feeds.

Conclusion

These 50 prompts cover the spectrum of Vue.js and Nuxt development—from simple component scaffolding to advanced state management and real-time communication. By integrating prompt-driven generation into your workflow, you can reduce boilerplate, enforce consistency, and focus on application logic that truly matters.

Remember that prompts are not a replacement for understanding the underlying framework. Always review generated code, adapt it to your project’s conventions, and test thoroughly. The examples above are starting points; feel free to modify them with your own business logic, styling preferences, and error handling patterns.

Whether you are building a small dashboard or a large-scale Nuxt application, these prompts will help you ship faster without sacrificing code quality. Save this collection, and the next time you face a repetitive task, let the AI handle the scaffolding while you craft the unique value of your application.

← All posts

Comments