Vue.js Intermediate Course - Modern Frontend Development 2025 | LearnFast
programmingintermediate
Last updated: February 12, 2025

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.

Master Vue.js Development →

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.