
Vue.js Intermediate Course: Modern Frontend Development in 2025
Vue.js has evolved into one of the most developer-friendly and powerful frontend frameworks, offering the perfect balance between simplicity and sophistication. At the intermediate level, Vue.js development goes beyond basic component creation to encompass advanced patterns, state management, and architectural decisions that enable building scalable, maintainable applications.
This comprehensive guide explores the advanced Vue.js concepts that transform you from a functional Vue developer into someone who can architect complex applications with confidence. You'll master the Composition API, advanced component patterns, state management with Pinia, and performance optimization techniques that are essential for professional Vue.js development.
Whether you're building complex single-page applications, working on team projects, or preparing for senior frontend roles, these intermediate concepts will elevate your Vue.js skills to meet real-world development challenges and modern application requirements.
Master intermediate Vue.js concepts with the Composition API, advanced component patterns, and state management techniques for building professional applications.
Understanding Modern Vue.js Architecture
Vue.js 3 represents a significant evolution in how we think about and structure frontend applications. The introduction of the Composition API, improved TypeScript support, and better performance characteristics require rethinking traditional Vue.js development approaches.
Modern Vue.js development emphasizes composition over inheritance, reactive programming patterns, and modular architecture that scales from small components to large applications. Understanding these paradigms is crucial for intermediate-level development.
The framework's design philosophy of progressive enhancement means you can adopt advanced features gradually while maintaining the simplicity that makes Vue.js appealing to developers at all skill levels.
Vue.js 3 Core Improvements
Performance Enhancements include a smaller bundle size, faster rendering through the new reactivity system, and better tree-shaking that eliminates unused code from production builds.
Composition API provides better logic reuse, improved TypeScript support, and more flexible component organization compared to the Options API approach.
Fragment Support allows components to have multiple root nodes, providing greater flexibility in component structure and reducing unnecessary wrapper elements.
Teleport Component enables rendering content outside the component hierarchy, essential for modals, tooltips, and other overlay components.
Vue.js Trends to Watch in 2025
The Vue.js ecosystem continues evolving rapidly, with new patterns, tools, and architectural approaches reshaping how developers build modern web applications.
Server-Side Rendering Revolution with Nuxt 3 and Vue's built-in SSR capabilities becoming the default for production applications, emphasizing performance, SEO, and user experience while maintaining the developer experience Vue.js is known for.
Micro-Frontend Architecture adoption is growing as Vue.js applications scale, with tools and patterns emerging for building modular, independently deployable frontend components that can be composed into larger applications.
Enhanced Developer Experience through improved tooling including Vite's lightning-fast development server, better debugging tools, and AI-powered development assistants that understand Vue.js patterns and best practices.
Composition API Ecosystem Maturity brings sophisticated composables libraries, standardized patterns for logic reuse, and better integration with external libraries, making the Composition API the preferred approach for complex applications.
Performance-First Development emphasizes Core Web Vitals optimization, automatic code splitting, and runtime performance monitoring as default practices rather than afterthoughts in Vue.js application development.
Staying current with these trends will help you build more efficient, scalable, and maintainable Vue.js applications while leveraging cutting-edge tools that enhance both developer productivity and user experience.
Mastering the Composition API
The Composition API represents a fundamental shift in how Vue.js components are structured and how logic is organized. It provides better code reuse, improved TypeScript support, and more flexible component architecture.
Reactive Programming with Composition API
Ref and Reactive provide different approaches to creating reactive data, with ref
for primitive values and reactive
for complex objects.
import { ref, reactive, computed } from "vue";
export default {
setup() {
// Reactive primitives
const count = ref(0);
const name = ref("Vue.js");
// Reactive objects
const user = reactive({
id: 1,
name: "John Doe",
email: "john@example.com",
});
// Computed properties
const doubleCount = computed(() => count.value * 2);
const displayName = computed(() => `Hello, ${name.value}!`);
return {
count,
name,
user,
doubleCount,
displayName,
};
},
};
Computed Properties and Watchers in the Composition API provide fine-grained control over reactive dependencies and side effects.
import { ref, computed, watch, watchEffect } from "vue";
export default {
setup() {
const searchQuery = ref("");
const searchResults = ref([]);
const isLoading = ref(false);
// Computed property with getter and setter
const filteredResults = computed({
get: () =>
searchResults.value.filter((item) =>
item.title.toLowerCase().includes(searchQuery.value.toLowerCase())
),
set: (value) => {
searchResults.value = value;
},
});
// Watcher with immediate and deep options
watch(
searchQuery,
async (newQuery, oldQuery) => {
if (newQuery !== oldQuery && newQuery.length > 2) {
isLoading.value = true;
try {
const results = await searchAPI(newQuery);
searchResults.value = results;
} finally {
isLoading.value = false;
}
}
},
{ immediate: true }
);
// Effect that runs immediately and re-runs when dependencies change
watchEffect(() => {
document.title = searchQuery.value
? `Search: ${searchQuery.value}`
: "Search Page";
});
return {
searchQuery,
searchResults,
isLoading,
filteredResults,
};
},
};
Lifecycle Hooks in Composition API
Lifecycle Management in the Composition API provides more flexibility and better testing capabilities compared to the Options API.
import {
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount,
} from "vue";
export default {
setup() {
let timer = null;
onBeforeMount(() => {
console.log("Component is about to mount");
});
onMounted(() => {
console.log("Component mounted");
timer = setInterval(() => {
console.log("Timer tick");
}, 1000);
});
onUpdated(() => {
console.log("Component updated");
});
onBeforeUnmount(() => {
console.log("Component about to unmount");
if (timer) {
clearInterval(timer);
}
});
onUnmounted(() => {
console.log("Component unmounted");
});
return {};
},
};
Composables for Logic Reuse
Custom Composables enable extracting and reusing reactive logic across multiple components, promoting code reuse and separation of concerns.
// useCounter.js
import { ref, computed } from "vue";
export function useCounter(initialValue = 0, step = 1) {
const count = ref(initialValue);
const increment = () => {
count.value += step;
};
const decrement = () => {
count.value -= step;
};
const reset = () => {
count.value = initialValue;
};
const isPositive = computed(() => count.value > 0);
const isNegative = computed(() => count.value < 0);
return {
count: readonly(count),
increment,
decrement,
reset,
isPositive,
isNegative,
};
}
// useLocalStorage.js
import { ref, watch } from "vue";
export function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key);
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue);
watch(
value,
(newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
},
{ deep: true }
);
return value;
}
Advanced Component Patterns
Intermediate Vue.js development requires understanding sophisticated component patterns that enable building flexible, reusable, and maintainable component libraries.
Render Functions and JSX
Render Functions provide programmatic component creation for complex scenarios where templates become limiting.
import { h, ref } from "vue";
export default {
props: ["level"],
setup(props, { slots }) {
const count = ref(0);
return () =>
h(
`h${props.level}`,
{
class: "dynamic-heading",
onClick: () => count.value++,
},
[
slots.default?.() || "Default heading",
h("span", ` (clicked ${count.value} times)`),
]
);
},
};
JSX Integration enables React-like component syntax while maintaining Vue.js reactivity and component features.
import { defineComponent, ref } from "vue";
export default defineComponent({
props: ["items"],
setup(props) {
const selectedItem = ref(null);
const handleSelect = (item) => {
selectedItem.value = item;
};
return () => (
<div class="item-list">
{props.items.map((item) => (
<div
key={item.id}
class={["item", { selected: selectedItem.value?.id === item.id }]}
onClick={() => handleSelect(item)}
>
{item.name}
</div>
))}
</div>
);
},
});
Higher-Order Components and Mixins
Higher-Order Components enable wrapping components with additional functionality while maintaining clean separation of concerns.
// withLoading.js
import { defineComponent, h } from "vue";
export function withLoading(WrappedComponent) {
return defineComponent({
props: {
isLoading: Boolean,
...WrappedComponent.props,
},
setup(props, context) {
return () => {
if (props.isLoading) {
return h("div", { class: "loading-spinner" }, "Loading...");
}
return h(WrappedComponent, props, context.slots);
};
},
});
}
// Usage
const MyComponentWithLoading = withLoading(MyComponent);
Composition API Mixins provide better alternatives to traditional mixins through composable functions.
// useFetchData.js
import { ref, onMounted } from "vue";
export function useFetchData(url) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const fetchData = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
data.value = await response.json();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
onMounted(fetchData);
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
refetch: fetchData,
};
}
Slot Patterns and Scoped Slots
Advanced Slot Patterns enable creating flexible, reusable components that adapt to different content requirements.
<template>
<div class="flexible-list">
<div v-if="$slots.header" class="list-header">
<slot name="header" :itemCount="items.length" />
</div>
<div class="list-body">
<div v-for="(item, index) in items" :key="item.id" class="list-item">
<slot
name="item"
:item="item"
:index="index"
:isFirst="index === 0"
:isLast="index === items.length - 1"
:select="() => selectItem(item)"
:remove="() => removeItem(item)"
/>
</div>
</div>
<div v-if="$slots.footer" class="list-footer">
<slot name="footer" :selectedItems="selectedItems" />
</div>
</div>
<script>
export default {
props: ["items"],
setup(props) {
const selectedItems = ref([]);
const selectItem = (item) => {
if (!selectedItems.value.includes(item)) {
selectedItems.value.push(item);
}
};
const removeItem = (item) => {
const index = selectedItems.value.indexOf(item);
if (index > -1) {
selectedItems.value.splice(index, 1);
}
};
return {
selectedItems,
selectItem,
removeItem,
};
},
};
</script>
State Management with Pinia
Pinia represents the next generation of state management for Vue.js applications, offering better TypeScript support, improved developer experience, and more intuitive APIs compared to Vuex.
Pinia Store Architecture
Store Definition with Pinia provides a clean, intuitive API for defining application state and actions.
// stores/userStore.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useUserStore = defineStore("user", () => {
// State
const currentUser = ref(null);
const users = ref([]);
const loading = ref(false);
// Getters (computed)
const isAuthenticated = computed(() => !!currentUser.value);
const userById = computed(
() => (id) => users.value.find((user) => user.id === id)
);
const activeUsers = computed(() =>
users.value.filter((user) => user.isActive)
);
// Actions
async function login(credentials) {
loading.value = true;
try {
const response = await authAPI.login(credentials);
currentUser.value = response.user;
return response;
} catch (error) {
throw error;
} finally {
loading.value = false;
}
}
function logout() {
currentUser.value = null;
// Clear sensitive data
}
async function fetchUsers() {
loading.value = true;
try {
const response = await userAPI.getUsers();
users.value = response.data;
} catch (error) {
console.error("Failed to fetch users:", error);
} finally {
loading.value = false;
}
}
function updateUser(userId, updates) {
const index = users.value.findIndex((user) => user.id === userId);
if (index !== -1) {
users.value[index] = { ...users.value[index], ...updates };
}
}
return {
// State
currentUser: readonly(currentUser),
users: readonly(users),
loading: readonly(loading),
// Getters
isAuthenticated,
userById,
activeUsers,
// Actions
login,
logout,
fetchUsers,
updateUser,
};
});
Store Composition and Modularity
Store Composition enables building complex state management through multiple, focused stores that can interact with each other.
// stores/todoStore.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { useUserStore } from "./userStore";
export const useTodoStore = defineStore("todo", () => {
const userStore = useUserStore();
const todos = ref([]);
const filter = ref("all"); // all, active, completed
const filteredTodos = computed(() => {
switch (filter.value) {
case "active":
return todos.value.filter((todo) => !todo.completed);
case "completed":
return todos.value.filter((todo) => todo.completed);
default:
return todos.value;
}
});
const todosForCurrentUser = computed(() => {
if (!userStore.isAuthenticated) return [];
return todos.value.filter(
(todo) => todo.userId === userStore.currentUser.id
);
});
function addTodo(text) {
if (!userStore.isAuthenticated) {
throw new Error("User must be authenticated to add todos");
}
const todo = {
id: Date.now(),
text,
completed: false,
userId: userStore.currentUser.id,
createdAt: new Date(),
};
todos.value.push(todo);
}
function toggleTodo(id) {
const todo = todos.value.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}
function setFilter(newFilter) {
filter.value = newFilter;
}
return {
todos: readonly(todos),
filter: readonly(filter),
filteredTodos,
todosForCurrentUser,
addTodo,
toggleTodo,
setFilter,
};
});
Store Plugins and Persistence
Store Plugins extend Pinia functionality with cross-cutting concerns like persistence, logging, and synchronization.
// plugins/persistence.js
export function persistencePlugin({ store }) {
const storageKey = `pinia-${store.$id}`;
// Load initial state from localStorage
const savedState = localStorage.getItem(storageKey);
if (savedState) {
store.$patch(JSON.parse(savedState));
}
// Save state changes to localStorage
store.$subscribe((mutation, state) => {
localStorage.setItem(storageKey, JSON.stringify(state));
});
}
// main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import { persistencePlugin } from "./plugins/persistence";
const pinia = createPinia();
pinia.use(persistencePlugin);
const app = createApp(App);
app.use(pinia);
Advanced Vue.js Patterns
Professional Vue.js development requires understanding sophisticated patterns that solve common architectural challenges while maintaining code quality and maintainability.
Provide/Inject Pattern
Dependency Injection enables passing data and methods down component trees without prop drilling, particularly useful for design systems and plugin architecture.
// Parent component
import { provide, ref } from 'vue'
export default {
setup() {
const theme = ref('light')
const notifications = ref([])
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
const addNotification = (message, type = 'info') => {
const id = Date.now()
notifications.value.push({ id, message, type })
setTimeout(() => {
removeNotification(id)
}, 5000)
}
const removeNotification = (id) => {
const index = notifications.value.findIndex(n => n.id === id)
if (index > -1) {
notifications.value.splice(index, 1)
}
}
// Provide theme system
provide('theme', {
current: readonly(theme),
toggle: toggleTheme
})
// Provide notification system
provide('notifications', {
list: readonly(notifications),
add: addNotification,
remove: removeNotification
})
return {
theme,
notifications
}
}
}
// Child component (any level deep)
import { inject } from 'vue'
export default {
setup() {
const theme = inject('theme')
const notifications = inject('notifications')
const handleAction = () => {
notifications.add('Action completed successfully', 'success')
}
return {
theme,
notifications,
handleAction
}
}
}
Teleport and Portal Patterns
Teleport Component enables rendering content outside the component hierarchy, essential for modals, tooltips, and overlay components.
<template>
<Teleport to="body">
<Transition name="modal">
<div v-if="visible" class="modal-overlay" @click="close">
<div class="modal-content" @click.stop>
<header class="modal-header">
<slot name="header">
<h3>{{ title }}
</slot>
<button @click="close" class="close-button">Ć</button>
</header>
<main class="modal-body">
<slot />
</main>
<footer v-if="$slots.footer" class="modal-footer">
<slot name="footer" />
</footer>
</div>
</div>
</Transition>
</Teleport>
<script>
import { watch } from "vue";
export default {
props: {
visible: Boolean,
title: String,
closeOnEscape: {
type: Boolean,
default: true,
},
},
emits: ["close"],
setup(props, { emit }) {
const close = () => {
emit("close");
};
const handleEscape = (event) => {
if (event.key === "Escape" && props.closeOnEscape) {
close();
}
};
watch(
() => props.visible,
(visible) => {
if (visible) {
document.addEventListener("keydown", handleEscape);
document.body.style.overflow = "hidden";
} else {
document.removeEventListener("keydown", handleEscape);
document.body.style.overflow = "";
}
}
);
return {
close,
};
},
};
</script>
Plugin Development
Custom Plugin Creation enables packaging and distributing Vue.js functionality as reusable plugins.
// plugins/toast.js
import { createApp, ref } from "vue";
import ToastComponent from "./ToastComponent.vue";
export default {
install(app, options = {}) {
const toasts = ref([]);
const addToast = (message, type = "info", duration = 3000) => {
const id = Date.now();
const toast = { id, message, type, duration };
toasts.value.push(toast);
if (duration > 0) {
setTimeout(() => {
removeToast(id);
}, duration);
}
return id;
};
const removeToast = (id) => {
const index = toasts.value.findIndex((t) => t.id === id);
if (index > -1) {
toasts.value.splice(index, 1);
}
};
// Global property
app.config.globalProperties.$toast = {
success: (message, duration) => addToast(message, "success", duration),
error: (message, duration) => addToast(message, "error", duration),
warning: (message, duration) => addToast(message, "warning", duration),
info: (message, duration) => addToast(message, "info", duration),
};
// Provide for Composition API
app.provide("toast", {
add: addToast,
remove: removeToast,
toasts: readonly(toasts),
});
// Mount toast container
const toastContainer = document.createElement("div");
toastContainer.id = "toast-container";
document.body.appendChild(toastContainer);
const toastApp = createApp(ToastComponent, { toasts });
toastApp.mount(toastContainer);
},
};
Performance Optimization
Vue.js applications require strategic performance optimization to ensure smooth user experiences, especially as applications grow in complexity and data volume.
Component Optimization
Lazy Loading and Code Splitting reduce initial bundle size and improve load times through strategic component loading.
import { defineAsyncComponent } from "vue";
// Lazy load components
const LazyComponent = defineAsyncComponent(() => import("./LazyComponent.vue"));
// With loading and error states
const LazyComponentWithStates = defineAsyncComponent({
loader: () => import("./HeavyComponent.vue"),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000,
});
// Route-level code splitting
const routes = [
{
path: "/dashboard",
component: () => import("./views/Dashboard.vue"),
},
{
path: "/profile",
component: () => import("./views/Profile.vue"),
},
];
Memoization and Caching prevent unnecessary computations and re-renders through strategic use of computed properties and component memoization.
import { computed, shallowRef, triggerRef } from "vue";
export default {
setup() {
// Expensive computation
const expensiveData = shallowRef([]);
// Memoized computed property
const processedData = computed(() => {
return expensiveData.value
.filter((item) => item.isActive)
.map((item) => ({
...item,
processedAt: Date.now(),
}))
.sort((a, b) => a.priority - b.priority);
});
// Batch updates for performance
const updateData = (newItems) => {
expensiveData.value.push(...newItems);
triggerRef(expensiveData);
};
return {
processedData,
updateData,
};
},
};
Virtual Scrolling and Large Lists
Virtual Scrolling enables handling large datasets efficiently by only rendering visible items.
<template>
<div
ref="container"
class="virtual-list"
:style="{ height: containerHeight + 'px' }"
@scroll="handleScroll"
>
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<div
v-for="item in visibleItems"
:key="item.id"
class="list-item"
:style="{
position: 'absolute',
top: item.offsetTop + 'px',
height: itemHeight + 'px',
}"
>
<slot :item="item.data" :index="item.index" />
</div>
</div>
</div>
<script>
import { ref, computed, onMounted } from "vue";
export default {
props: {
items: Array,
itemHeight: {
type: Number,
default: 50,
},
containerHeight: {
type: Number,
default: 400,
},
},
setup(props) {
const container = ref(null);
const scrollTop = ref(0);
const totalHeight = computed(() => props.items.length * props.itemHeight);
const visibleItems = computed(() => {
const startIndex = Math.floor(scrollTop.value / props.itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(props.containerHeight / props.itemHeight) + 1,
props.items.length
);
return props.items.slice(startIndex, endIndex).map((item, index) => ({
id: item.id || startIndex + index,
data: item,
index: startIndex + index,
offsetTop: (startIndex + index) * props.itemHeight,
}));
});
const handleScroll = (event) => {
scrollTop.value = event.target.scrollTop;
};
return {
container,
totalHeight,
visibleItems,
handleScroll,
};
},
};
</script>
Testing Vue.js Applications
Comprehensive testing strategies ensure Vue.js application reliability and maintainability while supporting confident refactoring and feature development.
Component Testing
Vue Test Utils provides the foundation for testing Vue.js components with proper isolation and realistic rendering.
import { mount } from "@vue/test-utils";
import { describe, it, expect, vi } from "vitest";
import UserProfile from "@/components/UserProfile.vue";
describe("UserProfile", () => {
const mockUser = {
id: 1,
name: "John Doe",
email: "john@example.com",
avatar: "avatar.jpg",
};
it("renders user information correctly", () => {
const wrapper = mount(UserProfile, {
props: { user: mockUser },
});
expect(wrapper.find('[data-testid="user-name"]').text()).toBe(
mockUser.name
);
expect(wrapper.find('[data-testid="user-email"]').text()).toBe(
mockUser.email
);
expect(wrapper.find('[data-testid="user-avatar"]').attributes("src")).toBe(
mockUser.avatar
);
});
it("emits edit event when edit button is clicked", async () => {
const wrapper = mount(UserProfile, {
props: { user: mockUser },
});
await wrapper.find('[data-testid="edit-button"]').trigger("click");
expect(wrapper.emitted("edit")).toBeTruthy();
expect(wrapper.emitted("edit")[0]).toEqual([mockUser]);
});
it("handles loading state correctly", () => {
const wrapper = mount(UserProfile, {
props: {
user: mockUser,
loading: true,
},
});
expect(wrapper.find('[data-testid="loading-spinner"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="user-content"]').exists()).toBe(false);
});
});
Composables Testing
Composable Testing ensures reactive logic works correctly in isolation from component rendering.
import { describe, it, expect } from "vitest";
import { useCounter } from "@/composables/useCounter";
describe("useCounter", () => {
it("initializes with default value", () => {
const { count, isPositive, isNegative } = useCounter();
expect(count.value).toBe(0);
expect(isPositive.value).toBe(false);
expect(isNegative.value).toBe(false);
});
it("initializes with custom value and step", () => {
const { count } = useCounter(10, 2);
expect(count.value).toBe(10);
});
it("increments correctly", () => {
const { count, increment } = useCounter(0, 5);
increment();
expect(count.value).toBe(5);
increment();
expect(count.value).toBe(10);
});
it("computes positive and negative states correctly", () => {
const { count, isPositive, isNegative, increment, decrement } =
useCounter();
increment();
expect(isPositive.value).toBe(true);
expect(isNegative.value).toBe(false);
decrement();
decrement();
expect(isPositive.value).toBe(false);
expect(isNegative.value).toBe(true);
});
});
End-to-End Testing
E2E Testing with tools like Cypress or Playwright ensures complete user workflows function correctly across the entire application.
// cypress/e2e/user-management.cy.js
describe("User Management", () => {
beforeEach(() => {
cy.visit("/users");
cy.intercept("GET", "/api/users", { fixture: "users.json" }).as("getUsers");
cy.intercept("POST", "/api/users", { fixture: "newUser.json" }).as(
"createUser"
);
});
it("displays users list", () => {
cy.wait("@getUsers");
cy.get('[data-testid="user-list"]').should("be.visible");
cy.get('[data-testid="user-item"]').should("have.length.greaterThan", 0);
});
it("creates new user through form", () => {
cy.get('[data-testid="add-user-button"]').click();
cy.get('[data-testid="user-form"]').should("be.visible");
cy.get('[data-testid="name-input"]').type("New User");
cy.get('[data-testid="email-input"]').type("newuser@example.com");
cy.get('[data-testid="submit-button"]').click();
cy.wait("@createUser");
cy.get('[data-testid="success-message"]').should(
"contain",
"User created successfully"
);
});
it("handles form validation errors", () => {
cy.get('[data-testid="add-user-button"]').click();
cy.get('[data-testid="submit-button"]').click();
cy.get('[data-testid="name-error"]').should("contain", "Name is required");
cy.get('[data-testid="email-error"]').should(
"contain",
"Email is required"
);
});
});
Vue.js Ecosystem Integration
Modern Vue.js development involves integrating with various ecosystem tools and libraries that enhance development experience and application capabilities.
Vue Router Advanced Patterns
Route Guards and Navigation provide fine-grained control over application navigation and access control.
import { createRouter, createWebHistory } from "vue-router";
import { useUserStore } from "@/stores/userStore";
const routes = [
{
path: "/",
component: () => import("@/views/Home.vue"),
meta: { requiresAuth: false },
},
{
path: "/dashboard",
component: () => import("@/views/Dashboard.vue"),
meta: { requiresAuth: true, roles: ["user", "admin"] },
},
{
path: "/admin",
component: () => import("@/views/Admin.vue"),
meta: { requiresAuth: true, roles: ["admin"] },
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
// Global navigation guard
router.beforeEach((to, from, next) => {
const userStore = useUserStore();
if (to.meta.requiresAuth && !userStore.isAuthenticated) {
next("/login");
return;
}
if (to.meta.roles && !to.meta.roles.includes(userStore.currentUser?.role)) {
next("/unauthorized");
return;
}
next();
});
// Route-specific guards
const routes = [
{
path: "/profile/:id",
component: ProfileView,
beforeEnter: (to, from, next) => {
const userStore = useUserStore();
const profileId = to.params.id;
// Only allow access to own profile or admin access
if (
userStore.currentUser?.id === profileId ||
userStore.currentUser?.role === "admin"
) {
next();
} else {
next("/unauthorized");
}
},
},
];
TypeScript Integration
TypeScript with Vue.js provides enhanced development experience through static typing and better tooling support.
// types/user.ts
export interface User {
id: string
name: string
email: string
role: 'user' | 'admin'
createdAt: Date
isActive: boolean
}
export interface CreateUserData {
name: string
email: string
password: string
}
// components/UserForm.vue
<template>
<form @submit.prevent="handleSubmit">
<input
v-model="formData.name"
type="text"
placeholder="Name"
required
/>
<input
v-model="formData.email"
type="email"
placeholder="Email"
required
/>
<button type="submit" :disabled="loading">
{{ loading ? 'Creating...' : 'Create User' }}
</button>
</form>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { CreateUserData, User } from '@/types/user'
interface Props {
loading?: boolean
}
interface Emits {
(e: 'submit', data: CreateUserData): void
(e: 'cancel'): void
}
const props = withDefaults(defineProps<Props>(), {
loading: false
})
const emit = defineEmits<Emits>()
const formData = reactive<CreateUserData>({
name: '',
email: '',
password: ''
})
const handleSubmit = () => {
emit('submit', { ...formData })
}
</script>
Build Tool Integration
Vite Configuration for optimal development and build performance with Vue.js applications.
// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true,
propsDestructure: true,
},
}),
],
resolve: {
alias: {
"@": resolve(__dirname, "src"),
"@components": resolve(__dirname, "src/components"),
"@stores": resolve(__dirname, "src/stores"),
"@utils": resolve(__dirname, "src/utils"),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
},
},
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ["vue", "vue-router", "pinia"],
ui: ["@headlessui/vue", "@heroicons/vue"],
},
},
},
},
test: {
environment: "happy-dom",
setupFiles: ["./src/test/setup.ts"],
},
});
Mastering intermediate Vue.js development requires combining deep framework knowledge with ecosystem understanding and professional development practices that enable building scalable, maintainable applications.
To advance your Vue.js expertise, strengthen your JavaScript foundation with JavaScript Intermediate, explore framework comparisons through React vs Vue Comparison, and enhance applications with TypeScript Integration.
For comprehensive documentation and community resources, study the Vue.js Official Guide for latest features and best practices, and contribute to or learn from the Vue.js GitHub Repository for cutting-edge developments and community discussions.
Frequently Asked Questions
Should I use the Options API or Composition API for new Vue.js projects? The Composition API is recommended for new projects, especially those using TypeScript or requiring complex logic reuse. It provides better code organization, improved TypeScript support, and easier testing. However, the Options API remains fully supported and might be preferable for simpler components or teams transitioning from Vue 2.
How does Pinia compare to Vuex for state management? Pinia is the recommended state management solution for Vue.js 3, offering better TypeScript support, simpler API, and improved developer experience. It eliminates mutations, provides automatic code splitting, and has better composition with the Composition API. Vuex remains supported but Pinia is preferred for new projects.
What's the best way to handle forms in Vue.js applications? Use v-model for simple forms, implement custom validation logic with reactive refs and computed properties, or adopt form libraries like VeeValidate for complex scenarios. The Composition API enables creating reusable form logic through composables that handle validation, submission, and error states.
How do I optimize Vue.js applications for production? Implement code splitting through dynamic imports, use lazy loading for routes and components, optimize bundle size with tree shaking, enable compression, implement virtual scrolling for large lists, and use performance monitoring tools. Vite's build optimizations handle many optimizations automatically.
What's the migration path from Vue 2 to Vue 3? Start with the Vue 3 Migration Guide, use the migration build for gradual transition, update dependencies to Vue 3 compatible versions, migrate from Vuex to Pinia, adopt the Composition API gradually, and update build tools to Vite. Large applications should migrate incrementally rather than all at once.
Intermediate Vue.js development combines deep framework knowledge with ecosystem mastery to build sophisticated, performant applications. Focus on the Composition API, Pinia state management, and modern development patterns to create maintainable applications that scale with your project requirements and team growth.
Related Learning Paths

Full-Stack Development Advanced - End-to-End Web Applications 2025
Master full-stack development in 2025. Build complete web applications with 40% less code using our 6-step architecture framework.

JavaScript Intermediate Course - Master Modern JS Development 2025
Advance your JavaScript skills with ES6+, async programming, and modern frameworks. Build professional web applications and land developer jobs.

Next.js Intermediate Course - Build Production React Apps 2025
Master Next.js in 2025 and build React applications that load 3x faster. Learn 6 advanced techniques for scalable, SEO-friendly sites.

Node.js for Beginners - Backend JavaScript Development 2025
Master Node.js in 2025 and build powerful backend applications. Learn 5 core concepts to create APIs that handle 3x more requests.

React Intermediate Course - Build Modern Web Apps 2025
Master 7 intermediate React concepts for 2025: hooks, context, and state management. Build 3x faster apps with our optimization guide.

TypeScript Intermediate - Type-Safe JavaScript Development 2025
Master TypeScript in 2025 and reduce bugs by 70%. Learn 5 advanced type techniques and best practices for enterprise applications.