AI-Powered Vue.js Development: A Practical Guide to Smarter Coding with Agents
Learn how to leverage AI assistants and agents effectively in Vue.js development to write better code, accelerate development, and maintain best practices without losing control of your codebase.
Felipe Leite
AI coding assistants have transformed how we write software, but using them effectively—especially with Vue.js—requires strategy. In this guide, I'll share practical approaches to using AI tools like Claude, GitHub Copilot, and AI agents to enhance your Vue.js development without compromising code quality.
Phase 1: Understanding AI's Role in Vue.js Development
Before diving into specific techniques, it's crucial to understand what AI can and cannot do well in Vue.js projects.
What AI Excels At
- ✅ Boilerplate Generation — Creating component scaffolds, composables, and stores
- ✅ Pattern Recognition — Suggesting Vue 3 Composition API patterns
- ✅ Type Safety — Generating TypeScript interfaces and proper type annotations
- ✅ Code Refactoring — Converting Options API to Composition API
- ✅ Testing — Writing unit and component tests
- ✅ Documentation — Creating JSDoc comments and README files
What Requires Human Oversight
- ⚠️ Architecture Decisions — Choosing state management patterns
- ⚠️ Performance Optimization — Deciding when to use computed vs watch
- ⚠️ Security — Validating XSS prevention and input sanitization
- ⚠️ UX Logic — Implementing complex user interactions
- ⚠️ Accessibility — Ensuring ARIA attributes are correctly used
Phase 2: Setting Up Your AI-Powered Workflow
1. Choosing the Right AI Tools
Different AI tools serve different purposes in Vue.js development:
For Real-Time Coding:
- GitHub Copilot — Inline suggestions as you type
- Codeium — Free alternative with Vue-specific training
For Complex Tasks:
- Claude Code — Multi-file refactoring and architectural planning
- Cursor AI — Codebase-aware context and intelligent edits
For Code Review:
- AI PR Review Agents — Automated code review on pull requests
- SonarQube + AI — Security and quality analysis
2. Configuring AI for Vue.js Context
The key to getting good AI suggestions is providing proper context. Here's how I structure my projects:
/.ai/context.md:
# Project Context for AI Assistants
## Tech Stack
- Vue 3 with Composition API
- TypeScript (strict mode)
- Pinia for state management
- Vue Router 4
- Vite as build tool
## Code Standards
- Use `<script setup>` syntax
- Prefer composables over mixins
- TypeScript for all new files
- Test coverage minimum 80%
## File Structure
- `/components` — Reusable UI components
- `/composables` — Shared composition functions
- `/stores` — Pinia stores
- `/views` — Page-level components
This file helps AI understand your project conventions and generate more accurate suggestions.
Phase 3: Practical AI Usage Patterns
Pattern 1: Generating Composables with AI
Prompt Example:
Create a Vue 3 composable called `useDebounce` that:
- Takes a callback function and delay in ms
- Returns a debounced version of the function
- Properly cleans up on component unmount
- Uses TypeScript with proper generic types
AI-Generated Result:
// composables/useDebounce.ts
import { ref, onUnmounted } from "vue";
export function useDebounce<T extends (...args: any[]) => any>(
fn: T,
delay: number = 300,
) {
const timeoutId = ref<NodeJS.Timeout | null>(null);
const debouncedFn = (...args: Parameters<T>) => {
if (timeoutId.value) {
clearTimeout(timeoutId.value);
}
timeoutId.value = setTimeout(() => {
fn(...args);
}, delay);
};
onUnmounted(() => {
if (timeoutId.value) {
clearTimeout(timeoutId.value);
}
});
return debouncedFn;
}
✅ What to Check:
- Type safety is correct
- Cleanup happens in
onUnmounted - Function signature matches expectations
Pattern 2: Component Scaffolding
Prompt Example:
Create a Vue 3 component called `SearchBar` with:
- TypeScript + script setup
- Props: placeholder (string), modelValue (string)
- Emits: update:modelValue, search
- Debounced search with 300ms delay
- Clear button that appears when input has value
- Accessible with proper ARIA labels
AI-Generated Component:
<!-- components/SearchBar.vue -->
<script setup lang="ts">
import { computed } from "vue";
import { useDebounce } from "@/composables/useDebounce";
interface Props {
placeholder?: string;
modelValue: string;
}
interface Emits {
(e: "update:modelValue", value: string): void;
(e: "search", value: string): void;
}
const props = withDefaults(defineProps<Props>(), {
placeholder: "Search...",
});
const emit = defineEmits<Emits>();
const hasValue = computed(() => props.modelValue.length > 0);
const debouncedSearch = useDebounce((value: string) => {
emit("search", value);
}, 300);
const handleInput = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
emit("update:modelValue", value);
debouncedSearch(value);
};
const clearSearch = () => {
emit("update:modelValue", "");
emit("search", "");
};
</script>
<template>
<div class="search-bar">
<input
type="search"
:value="modelValue"
:placeholder="placeholder"
@input="handleInput"
aria-label="Search input"
class="search-input"
/>
<button
v-if="hasValue"
@click="clearSearch"
aria-label="Clear search"
class="clear-button"
>
×
</button>
</div>
</template>
<style scoped>
.search-bar {
position: relative;
display: flex;
align-items: center;
}
.search-input {
width: 100%;
padding: 0.5rem 2rem 0.5rem 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
font-size: 0.875rem;
}
.clear-button {
position: absolute;
right: 0.5rem;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #64748b;
}
</style>
✅ Human Review Checklist:
- Props and emits are properly typed
- v-model pattern is correctly implemented
- Accessibility attributes are present
- Debounce cleanup happens correctly
- Styles match design system
Pattern 3: Using AI Agents for Refactoring
AI agents (like Claude Code) can handle multi-file refactoring tasks that would be tedious manually.
Example Task:
Convert all components in /components/legacy from Options API to Composition API:
1. Maintain exact same functionality
2. Use TypeScript with proper types
3. Extract reusable logic into composables
4. Add unit tests for each refactored component
Agent Workflow:
- Analysis Phase — Agent reads all files and identifies patterns
- Planning Phase — Creates refactoring plan for review
- Execution Phase — Refactors files one by one
- Validation Phase — Runs tests to ensure no regressions
Phase 4: Advanced Techniques
Technique 1: AI-Assisted State Management
Prompt:
Create a Pinia store for user authentication with:
- TypeScript types
- Login/logout actions
- Token refresh logic
- Persistent state with localStorage
- Proper error handling
Generated Store:
// stores/auth.ts
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import type { User, LoginCredentials } from "@/types";
export const useAuthStore = defineStore("auth", () => {
const user = ref<User | null>(null);
const token = ref<string | null>(localStorage.getItem("auth_token"));
const isLoading = ref(false);
const error = ref<string | null>(null);
const isAuthenticated = computed(() => !!user.value && !!token.value);
async function login(credentials: LoginCredentials) {
isLoading.value = true;
error.value = null;
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(credentials),
});
if (!response.ok) {
throw new Error("Login failed");
}
const data = await response.json();
user.value = data.user;
token.value = data.token;
localStorage.setItem("auth_token", data.token);
} catch (e) {
error.value = e instanceof Error ? e.message : "An error occurred";
throw e;
} finally {
isLoading.value = false;
}
}
function logout() {
user.value = null;
token.value = null;
localStorage.removeItem("auth_token");
}
async function refreshToken() {
if (!token.value) return;
try {
const response = await fetch("/api/auth/refresh", {
headers: { Authorization: `Bearer ${token.value}` },
});
if (!response.ok) {
logout();
return;
}
const data = await response.json();
token.value = data.token;
localStorage.setItem("auth_token", data.token);
} catch {
logout();
}
}
return {
user,
token,
isLoading,
error,
isAuthenticated,
login,
logout,
refreshToken,
};
});
Technique 2: Test Generation with Context
Prompt:
Generate Vitest unit tests for the SearchBar component:
- Test v-model binding
- Test debounced search emission
- Test clear button functionality
- Test accessibility attributes
- Use Vue Testing Library
Generated Tests:
// components/__tests__/SearchBar.spec.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, fireEvent, screen } from "@testing-library/vue";
import SearchBar from "../SearchBar.vue";
describe("SearchBar", () => {
beforeEach(() => {
vi.useFakeTimers();
});
it("renders with placeholder", () => {
render(SearchBar, {
props: {
modelValue: "",
placeholder: "Search products...",
},
});
expect(
screen.getByPlaceholderText("Search products..."),
).toBeInTheDocument();
});
it("emits update:modelValue on input", async () => {
const { emitted } = render(SearchBar, {
props: { modelValue: "" },
});
const input = screen.getByRole("searchbox");
await fireEvent.update(input, "test query");
expect(emitted()["update:modelValue"]).toBeTruthy();
expect(emitted()["update:modelValue"][0]).toEqual(["test query"]);
});
it("emits debounced search event", async () => {
const { emitted } = render(SearchBar, {
props: { modelValue: "" },
});
const input = screen.getByRole("searchbox");
await fireEvent.update(input, "test");
expect(emitted().search).toBeFalsy();
vi.advanceTimersByTime(300);
expect(emitted().search).toBeTruthy();
expect(emitted().search[0]).toEqual(["test"]);
});
it("shows clear button when input has value", () => {
const { rerender } = render(SearchBar, {
props: { modelValue: "" },
});
expect(screen.queryByLabelText("Clear search")).not.toBeInTheDocument();
rerender({ modelValue: "test" });
expect(screen.getByLabelText("Clear search")).toBeInTheDocument();
});
it("clears input when clear button is clicked", async () => {
const { emitted } = render(SearchBar, {
props: { modelValue: "test query" },
});
const clearButton = screen.getByLabelText("Clear search");
await fireEvent.click(clearButton);
expect(emitted()["update:modelValue"][0]).toEqual([""]);
expect(emitted().search[0]).toEqual([""]);
});
it("has proper accessibility attributes", () => {
render(SearchBar, {
props: { modelValue: "test" },
});
expect(screen.getByLabelText("Search input")).toBeInTheDocument();
expect(screen.getByLabelText("Clear search")).toBeInTheDocument();
});
});
Phase 5: Best Practices and Pitfalls to Avoid
✅ DO's
- Always Review AI-Generated Code
- Check for security vulnerabilities
- Verify type safety
- Ensure it follows your project conventions
- Use AI for Repetitive Tasks
- Generating CRUD operations
- Creating form validation schemas
- Writing boilerplate tests
- Provide Clear Context
- Include relevant file paths in prompts
- Specify TypeScript requirements
- Mention testing framework preferences
- Iterate and Refine
- Start with a basic prompt
- Review the output
- Refine and regenerate if needed
❌ DON'Ts
- Don't Blindly Copy-Paste
- AI can make mistakes
- Generated code might not fit your architecture
- Security issues might be present
- Don't Skip Testing
- AI-generated code still needs tests
- Verify edge cases manually
- Run your test suite after AI changes
- Don't Use AI for Critical Security Logic
- Authentication flows need careful review
- Authorization logic should be human-verified
- Encryption/decryption requires expertise
- Don't Over-Engineer
- AI might suggest overly complex solutions
- Keep it simple unless complexity is warranted
- Prefer readable code over "clever" code
Phase 6: Real-World Example - Building a Feature with AI
Let's build a complete feature: a "Product Filter" component with AI assistance.
Step 1: Define Requirements
Create a product filtering system for an e-commerce site:
- Multi-select category filter
- Price range slider
- Search by name
- Sort options (price, name, date)
- Show active filter count
- Clear all filters button
- Persist filters in URL query params
Step 2: Generate Component Structure
Prompt to AI:
Generate a Vue 3 component structure for product filtering with:
1. FilterPanel.vue - Main filter component
2. useProductFilters composable - Filter logic
3. TypeScript types for filters
4. URL sync with Vue Router
Step 3: Review and Integrate
AI generates the components, and you:
- Review code for security issues
- Adjust styling to match design system
- Add missing edge cases
- Write additional tests
- Optimize performance if needed
Step 4: Refine with AI
The FilterPanel component is too large. Extract:
- PriceRangeSlider as separate component
- CategoryCheckboxes as separate component
- Maintain type safety and emit proper events
Conclusion
AI is a powerful tool for Vue.js development, but it's most effective when used strategically. Use it to handle boilerplate, accelerate development, and explore patterns—but always apply human judgment to architecture, security, and user experience decisions.
The key is finding the right balance: let AI handle the tedious work while you focus on the creative and critical aspects of building great applications.
Key Takeaways
- 🎯 Use AI for boilerplate, testing, and refactoring
- 🔍 Always review and test AI-generated code
- 📝 Provide clear context in your prompts
- 🛡️ Human oversight is essential for security and architecture
- 🔄 Iterate and refine AI outputs
- 🧪 Never skip testing AI-generated code
What's Next?
Experiment with different AI tools, find what works for your workflow, and remember: AI is your assistant, not your replacement. The best results come from human creativity combined with AI efficiency.
Real-Time Made Simple: How I Use Server-Sent Events (SSE) in Nuxt 3
A step-by-step guide to implementing real-time updates in Nuxt 3 using Server-Sent Events (SSE) as a lightweight alternative to WebSockets.
Supercharge React with HOC: How I Build Reusable Components the Smart Way
A step-by-step guide to creating Higher-Order Components (HOC) in React to handle cross-cutting concerns like authentication, error handling, and more.