The key features of Vue include:
- Declarative Rendering: Vue extends standardHTML with a template syntax that allows you to describe HTML output based on JavaScript state
- Reactivity: Vue automatically tracksJavaScript state changes and efficiently updates the DOM when changes happen.
- Component-Based Architecture: Applications are built using small, self-contained, and reusable components
-
Directives: Special attributes starting with
v-if,v-for, andv-bindthat apply reactive behavior to the DOM. - Virtual DOM: Vue uses a Virtual DOM to minimize actual DOM manipulations, improving performance.
- Gentle Learning Curve: If you know HTML, CSS,and JavaScript, you can start building with Vue quickly.
- Versatility: It can be used as a simple library to enhance static HTML or as a full-featured framework for complex SPAs.
- High Performance: Its lightweight nature and optimized re-rendering make it very fast.
- Great Documentation: Vue is famous in the dev community for having exceptionally clear and thorough official documentation.
As of January 2026, the latest stable version of Vue is 3.5.x. Vue 3 is the current industry standard, having replaced Vue 2 (which reached end-of-life in late 2023).
For modern development, the recommended way to start a Vue project is usingVite.
-
Run the creator tool: Open your terminal and run:
npm create vue@latest - Follow the prompts: Enter your project name and select features (like TypeScript or Vue Router).
- Install & Run:
- Options API: The traditional way where you define logic using an object of options like data, methods,and mounted. Best for smaller components.
- Composition API: Introduced in Vue 3, it uses the setup attribute or function to group logic by logical concern rather than option type. It is better for large-scale applications and reusability.
Directives are special tokens in the markup that tell the library to do something to a DOM element.
- v-bind: Dynamically binds an attribute (e.g.,:src="imagePath")
- v-model: Creates two-way data binding on form inputs
- v-if / v-else: Conditionally renders an element
- v-for: Renders a list of items by looping through an array
A Single-File Component is a file with a .vue extension that encapsulates the structure (HTML), logic (JavaScript), and styling (CSS) of a component in one place. Example (MyComponent.vue):
In large applications, passing data between many nested components becomes difficult. State management libraries provide a "central store" for all components.
- Pinia: The current officially recommended state management library for Vue 3. It is lightweight and has excellent TypeScript support.
- Vuex: The older state management library used primarily in Vue 2 projects.
Lifecycle hooks are built-in functions that allow you to execute code at specific stages of a component's existence, such as when it is created, added to the DOM, or destroyed.
- onMounted(): Called after the component has been mounted.
- onUpdated(): Called after the component has updated its DOM tree due to a state change.
- onUnmounted(): Called after the component has been removed.
Vue Router is the official router for Vue.js. It syncs the browser's URL with Vue's components, allowing you to build Single-Page Applications where the page doesn't refresh when the user navigates to different sections (like /about or /profile).
Vue Router is the official routing library for Vue.js. It integrates deeply with the Vue core to turn a collection of components into a Single-Page Application (SPA). It manages the relationship between the URL in the browser and the components displayed on the screen without requiring a full page reload.
Core Functionalities- Nested Mapping: Allows you to map nested URL paths to nested components.
- Dynamic Routing: Enables paths with parameters (e.g., /user/:id) to render the same component with different data.
- Navigation Guards: Provides hooks (beforeEach, beforeRouteEnter) to run logic, such as authentication checks, before a user enters a route.
- Transition Effects: Built-in support for CSS transitions when switching between routes.
- History Modes: Offers different ways to handle URLs (HTML5 History mode vs. Hash mode).
Key Components & Concepts
| Feature | Description |
|---|---|
<router-link> |
A component used to create navigation links. It renders
as an <a> tag but prevents page
refreshes.
|
<router-view> |
A functional component that acts as a placeholder where the matched component for the current route will be rendered. |
createRouter |
The function used to initialize the router instance with a list of routes and a history mode. |
| Programmatic Navigation |
Using router.push() or
router.replace() to navigate via JavaScript
instead of a link.
|
Comparison: Web Navigation Approaches
| Feature | Standard Multi-Page App (MPA) | Vue Router (SPA) |
|---|---|---|
| Page Load | Full refresh for every new URL. | Only components swap; no refresh. |
| State | State is lost unless saved in cookies/storage. | State is preserved in memory between views. |
| User Experience | Slower transitions due to server requests. | Instant transitions; feels like a desktop app. |
| SEO | Naturally SEO-friendly. | Requires SSR or pre-rendering for optimal SEO. |
Basic Implementation Example
- Define Routes:
- Display in Template:
Both v-if and v-show are used to
conditionally display elements in Vue.js. However, they differ
significantly in how they handle the DOM and when they should be
used for performance optimization.
Comparison Table
| Feature | v-if |
v-show |
|---|---|---|
| Mechanism | Real rendering: Elements are physically added or removed from the DOM. |
CSS-based:
Elements remain in the DOM; only the
display property is toggled.
|
| Initial Render | Lazy: If the condition is false initially, it does nothing until it becomes true. | Eager: The element is always rendered and held in the DOM regardless of the initial state. |
| Lifecycle Hooks |
Triggers mounted, unmounted,
and other lifecycle hooks of child components.
|
Does not trigger lifecycle hooks when toggling. |
| Performance (Toggle) | Higher cost: Expensive to toggle because it constantly destroys and re-creates elements. | Lower cost: Very cheap to toggle as it only switches CSS. |
| Performance (Initial) | Lower cost: Faster if the element is never shown. | Higher cost: Slower if you have many hidden elements on load. |
v-if(Conditional Rendering):- It is "real" conditional rendering because it ensures that event listeners and child components inside the conditional block are properly destroyed and re-created during toggles.
- It supports the
<template>element, allowing you to group multiple elements without adding an extra node to the DOM. - Works with
v-elseandv-else-if.
v-show(Conditional Visibility):- The element is always present in the DOM. Vue simply applies
display: none;to the inline styles when the expression evaluates to false. - It cannot be used on the
templateelement and does not work withv-else.
- The element is always present in the DOM. Vue simply applies
- Use
v-ifwhen:- The condition is unlikely to change frequently during the runtime.
- You want to reduce the initial load time by not rendering components that might never be seen.
- You need to use
v-elseorv-else-iflogic.
- Use
v-showwhen:- You need to toggle something very frequently (e.g., a tab, a tooltip, or a dropdown).
- The element is critical to the layout and should be "ready" in the DOM immediately.
The v-for directive is used to render a list of items based
on an array or an object. It uses a specific syntax:
item in items, where items is the source data
and item is an alias for the element being iterated.
-
Iterating Arrays:
You can access the element and its index:
v-for="(item, index) in items". -
Iterating Objects:
You can iterate through properties:
v-for="(value, key, index) in myObject". -
Range Support:
It can also take an integer to repeat a template a fixed number of
times:
v-for="n in 10".
The Role of the :key Attribute
The :key attribute is a unique identifier used by Vue’s
Virtual DOM algorithm to identify nodes when updating the list. Without
keys, Vue uses an "in-place patch" strategy, which can lead to UI bugs.
| Reason | Detail |
|---|---|
| Efficient Updates | It helps Vue identify exactly which items have changed, been added, or been removed, minimizing DOM manipulations. |
| State Preservation |
Ensures that temporary state (like text in an
<input> or focus) stays with the correct item
when the list is reordered.
|
| Identity | Provides a unique "signature" for each element so Vue doesn't accidentally reuse a component for a different piece of data. |
| Transitions |
Essential for Vue's
<transition-group> to correctly animate items
moving to new positions.
|
Best Practices vs. Anti-patterns
-
Do:
Use unique IDs from your database
(e.g.,
:key="user.id"). -
Don't:
Use
indexas a key if the list can be reordered, filtered, or deleted. Using the index can cause the wrong components to be updated or destroyed. -
Don't:
Use
v-foron the same element asv-if. Vue 3 givesv-ifhigher priority, meaning thev-ifcondition won't have access to variables defined in thev-for.
Implementation Example
In the Composition API, both ref and reactive are used to create reactive state. While they achieve similar goals, they handle data types and access patterns differently.
| Feature | ref |
reactive |
|---|---|---|
| Data Types | Handles any type (Primitives: String, Number, Boolean, or Objects). | Handles Objects and Arrays only (Complex types). |
| Access in Script | Must use .value property (e.g., count.value). |
Accessed directly (e.g., state.count). |
| Access in Template | Automatically unwrapped (no .value needed). |
Accessed directly. |
| Reassignment | Can replace the entire value (even objects). | Cannot replace the whole object; it loses reactivity if overwritten. |
| Destructuring | Maintains reactivity. | Loses reactivity unless used with toRefs. |
Detailed Breakdown
-
1. Handling Primitives
-
ref: Designed to wrap primitive values in an object to make them reactive.
- Example:
const count = ref(0).
- Example:
-
reactive: Cannot hold primitives. If you pass a string or number to
reactive(), it will trigger a Vue warning and won't be reactive.
-
ref: Designed to wrap primitive values in an object to make them reactive.
-
2. Reassignment Risks
-
ref: You can replace an entire object easily:
-
reactive: Overwriting the variable breaks the link to Vue's reactivity system:
-
ref: You can replace an entire object easily:
-
3. Destructuring Behavior
If you destructure a
reactiveobject, the resulting variables become plain values and stop updating the UI. To fix this, you must wrap the object intoRefs().refvariables do not suffer from this issue when passed around.
When to Use Which?
-
Use
refby default: It is more versatile because it handles primitives and objects. Most Vue developers preferreffor consistency. -
Use
reactivefor grouped state: If you have a large form or a collection of related properties that logically belong together in a single object,reactivecan make the code cleaner by avoiding.valueeverywhere.
Computed Properties Overview
Computed properties are reactive functions that allow you to describe a value that depends on other data properties. They are "derived" state—when the source data changes, the computed property automatically re-evaluates and updates.
Key Differences: Computed vs. Methods
The primary difference is caching. Computed properties cache their results based on their reactive dependencies, whereas methods execute every time they are called.
| Feature | Computed Properties | Methods |
|---|---|---|
| Caching | Cached: Only re-calculates when a dependency changes. | No Caching: Runs every time the component re-renders or it is called. |
| Usage | Used as a property (no parentheses: {{ fullName }}). |
Used as a function (with parentheses: {{ getFullName() }}). |
| Performance | Better for expensive logic or heavy data processing. | Can be less efficient if logic is complex and called frequently. |
| Purpose | Designed for data transformation or derivation. | Designed for event handling or specific actions. |
| Side Effects | Should be "pure" (no API calls or DOM changes). | Can contain side effects (API calls, timers, etc.). |
Technical Breakdown
- 1. The Power of Caching: If you have a computed property that filters a massive list, and that list hasn't changed, Vue will return the previously calculated result instantly. A method would force Vue to re-filter the entire list every time any unrelated part of the page updates.
-
2. Dependency Tracking: Vue's reactivity system tracks which variables are used inside a computed property. If
computedValuedepends onfirstNameandlastName, it will only run again if one of those two variables changes.
Best Practices
- Computed for Templates: Always use computed properties for complex logic inside your HTML to keep templates clean and readable.
- Avoid Side Effects: Never try to change other data properties or make asynchronous calls (like
fetch) inside a computed property; use Watchers for those tasks. - Getters and Setters: By default, computed properties are "getter-only," but you can provide a "setter" if you need to manually update the source data when the computed value is changed.
Implementation Example
Watchers Overview
In Vue.js, Watchers allow you to perform "side effects" in response to state changes. Unlike computed properties, which are used to derive new data, watchers are used for asynchronous operations, DOM manipulation, or changing other pieces of state when a specific dependency changes.
Comparison: watch vs. watchEffect
Vue 3 provides two main ways to watch data. While they share a similar goal, they differ in how they track dependencies and when they run for the first time.
| Feature | watch |
watchEffect |
|---|---|---|
| Tracking | Explicit: You must define exactly what to watch. | Implicit: Automatically tracks every reactive property used inside the function. |
| Initial Run | Lazy: Does not run until the watched source changes (can be overridden). | Eager: Runs immediately once the component is initialized. |
| Access to Old Value | Provides both the new value and the old value. | Only has access to the current state. |
| Use Case | Best for specific state changes or when you need the previous value. | Best for logic that depends on multiple sources or needs to run immediately. |
Key Concepts and Features
-
Deep Watching: By default,
watchis shallow. If you want to watch nested properties inside an object, you must set the{ deep: true }option. -
Immediate Execution: You can make a
watchrun immediately on setup (likewatchEffect) by passing{ immediate: true }. - Cleanup Side Effects: Both functions allow you to define a cleanup callback (e.g., to cancel an API request or clear a timer) that runs right before the effect re-runs or when the component is unmounted.
-
Flush Timing: You can control whether the watcher runs before or after Vue updates the DOM using the
flushoption ('pre','post', or'sync').
Practical Examples
Using watch (Specific and Detailed)
Using watchEffect (Automatic and Immediate)
When to use Watchers vs. Computed
- Computed: Use for transforming data to be displayed in the template (Pure functions).
- Watchers: Use for Side Effects such as API calls, logging, changing the URL, or manual DOM updates.
Two-Way Data Binding Overview
In Vue.js, two-way data binding refers to the synchronization between the user interface (View) and the underlying data (Model). When the data changes, the UI updates; conversely, when the user interacts with the UI (e.g., typing in an input), the data updates automatically.
The v-model directive is the primary tool used to achieve this. It acts as "syntactic sugar," combining property binding and event listening into a single attribute.
How v-model Works Internally
While v-model looks like a single operation, it is actually a shorthand for two separate actions:
- Binding a value: It passes a data property to the element (typically via the
valueattribute). - Listening for changes: It listens for an input event and updates the data property with the new value.
| Element Type | Property Bound | Event Listened To |
|---|---|---|
| Text Inputs/Textareas | value |
input event |
| Checkboxes/Radios | checked |
change event |
| Select Dropdowns | value |
change event |
Key Features and Modifiers
Vue provides built-in modifiers to change how v-model behaves during user interaction:
-
.lazy: Instead of updating the data on every keystroke (inputevent), it only updates when the user leaves the input field (changeevent). -
.number: Automatically casts the user input string into a valid JavaScript number. -
.trim: Automatically strips whitespace from the beginning and end of the user input.
Using v-model with Components
In Vue 3, v-model is highly flexible when used on custom components:
-
Default usage: Uses a prop named
modelValueand emits an event namedupdate:modelValue. -
Multiple v-models: You can bind multiple data points to a single component by specifying an argument (e.g.,
v-model:firstName="name"andv-model:lastName="surname").
Implementation Comparison
| Method | Code Example | Manual vs. Automatic |
|---|---|---|
| Manual Binding | <input :value="text" @input="text = $event.target.value"> |
Manual (verbose) |
| v-model | <input v-model="text"> |
Automatic (shorthand) |
Passing Data with Props
In Vue.js, data flows downward from parent components to child components using Props. Props are custom attributes you register on a component; when a value is passed to a prop attribute, it becomes a property on that component instance.
Key Characteristics of Props
- One-Way Data Flow: Data flows down, but not up. If the parent updates the prop, the child updates automatically. However, the child must not attempt to mutate a prop directly.
- Reactive: Props are reactive; if the parent's data changes, the child will reflect those changes immediately.
- Type Safety: You can define the expected data type (String, Number, Object, etc.) to ensure the component is used correctly.
Implementation Steps
| Step | Action | Description |
|---|---|---|
| 1. Define (Child) | defineProps() |
Use this macro in the child component to declare which props it expects. |
| 2. Pass (Parent) | v-bind or : |
Use the colon shorthand to pass dynamic data from the parent's state. |
| 3. Use (Child) | Template/Script | Access the prop in the child's template or script logic. |
Code Example
Child Component (Child.vue)
The child declares the props it is willing to receive.
Parent Component (Parent.vue)
The parent passes the data using the : shorthand.
Prop Validation Rules
It is a best practice to provide requirements for your props. If the requirements are not met, Vue will warn you in the browser console.
- Type: String, Number, Boolean, Array, Object, Date, Function, Symbol.
- Required:
{ required: true }. - Default:
{ default: 'Hello' }or a factory function for objects/arrays. - Validator: A custom function to validate the value logic.
Emitting Events Overview
In Vue.js, while Props allow a parent to send data down, Emits allow a child to send messages or data back up to the parent. This follows the core design pattern: "Props down, Events up".
When a child component triggers an event using $emit, the parent component can listen for that specific event and execute a method or update its own state in response.
Implementation Steps
| Step | Action | Description |
|---|---|---|
| 1. Declare (Child) | defineEmits() |
Declare the events the child is allowed to fire. |
| 2. Trigger (Child) | emit('name', payload) |
Fire the event, optionally passing data known as the payload. |
| 3. Listen (Parent) | v-on or @ |
Use the @ shorthand on the child component tag to catch the event. |
Detailed Code Example
Child Component (ChildButton.vue)
The child declares the event and triggers it on a button click.
Parent Component (ParentView.vue)
The parent listens for increaseBy and receives the payload automatically.
Key Rules and Best Practices
-
Kebab-case for Listeners: While you can use camelCase in JavaScript, Vue recommends using kebab-case in the HTML template (e.g.,
@update-value). -
Validation: You can validate emits in Vue 3 by passing an object to
defineEmitsthat checks if the payload is valid before sending it. - One-Way Data Flow: Never change a prop inside the child to communicate with the parent; always emit an event so the parent can change its own state.
-
Multiple Arguments: You can pass multiple arguments as a payload:
emit('update', arg1, arg2).
Teleport Overview
Teleport is a built-in component in Vue 3 that allows you to "transport" a piece of a component's template to a different part of the DOM tree, outside of the component's hierarchical structure.
Even though the element is rendered in a different location (like the <body> or a specific #container div), it still behaves like a normal child of the original component—meaning it can access the component's state, props, and scoped styles.
Why is Teleport Necessary?
In standard Vue development, components are rendered within their parents. However, certain UI elements can be broken by CSS properties like z-index, overflow: hidden, or position: relative in the parent container.
| Use Case | Problem Without Teleport | Solution With Teleport |
|---|---|---|
| Modals/Dialogs | May get cut off by overflow: hidden on a parent. |
Render at the top level of the <body> to avoid clipping. |
| Full-screen Overlays | Hard to manage z-index depth. |
Moves the element to a global container for easy layering. |
| Notifications | Needs to appear on top of all UI. | Teleport to a dedicated #toast-container at the root. |
| Tooltips | Positioning can be skewed by parent transform properties. |
Positions relative to the viewport or body. |
How to Use Teleport
The <Teleport> component requires a to target, which can be a CSS selector string (like an ID or class) or an actual DOM element.
Example: Creating a Modal
Key Features and Behaviors
- Logical vs. Physical: It remains a logical child of the component. You can pass props to it and emit events from it as if it hadn't moved.
-
Disabled State: You can toggle the teleportation using the
:disabledprop. When disabled, the content stays inside the original component structure. -
Multiple Teleports: Multiple
<Teleport>components can send content to the same target element. They will simply be appended one after another. -
Scoped CSS: Styles defined in
<style scoped>will still apply to the teleported content because it remains part of the virtual component tree.
Slots are a powerful distribution mechanism in Vue.js that allow a parent component to inject HTML, components, or plain text into a child component’s template. This makes components highly reusable by allowing the parent to define the "content" while the child defines the "layout".
Types of Slots
| Slot Type | Description | Usage Scenario |
|---|---|---|
| Default Slot | The unnamed, catch-all slot. | Simple wrappers like buttons or alerts. |
| Named Slots | Specific slots identified by a name (using v-slot:name or #name). |
Complex layouts with multiple sections (e.g., Header, Main, Footer). |
| Scoped Slots | Slots that pass data from the child back to the parent. | Lists or tables where the child manages data but the parent defines the UI. |
Understanding Scoped Slots
Normally, a parent cannot access data inside a child's scope. Scoped Slots bridge this gap by allowing the child to "provide" data to the slot's content in the parent.
The Workflow:
- Child: Defines a
<slot>and binds data to it using attributes (props). - Parent: Receives that data via the
v-slotdirective value.
Implementation Example
Child Component (UserList.vue):
The child iterates through data but lets the parent decide how to display each item.
Parent Component:
The parent accesses the userData via the #item slot.
Key Benefits of Scoped Slots
- Inversion of Control: The child manages the logic (fetching, filtering, or looping), while the parent manages the visual representation.
- Flexibility: You can use the same component for a "Card View" in one part of your app and a "Table View" in another, just by changing the slot template.
- Encapsulation: Keeps the child component focused on data/state management without hardcoding UI styles.
KeepAlive Overview
<KeepAlive> is a built-in component in Vue that caches component instances when they are dynamically toggled.
Normally, when a component is switched out (e.g., via v-if or <component :is="...">), it is destroyed and its state is lost. Wrapping these components in <KeepAlive> preserves their state and avoids the performance overhead of re-creating the DOM and re-fetching data.
Key Behaviors and Lifecycle
When a component is cached by <KeepAlive>, its standard lifecycle hooks behave differently, and two specialized hooks are introduced:
| Lifecycle Hook | Behavior Inside <KeepAlive> |
|---|---|
onMounted |
Only fires the first time the component is created. |
onUnmounted |
Never fires while the component is being cached. |
onActivated |
Fires every time the component becomes visible (re-enters the DOM). |
onDeactivated |
Fires every time the component is hidden (removed from DOM but kept in memory). |
Core Properties
You can control which components are cached using the following props:
include: Only components with matching names will be cached (String, Regex, or Array).exclude: Any component with a matching name will not be cached.max: Limits the maximum number of component instances to cache. When the limit is reached, the least recently used (LRU) instance is destroyed.
Common Use Cases
| Use Case | Benefit |
|---|---|
| Tabbed Interfaces | Switching between tabs preserves the user's scroll position and form input values. |
| List Views | Navigating back from a "Detail" page to a "List" page doesn't trigger a new API call to reload the list. |
| Multi-step Forms | Prevents data loss when a user navigates back to a previous step to make corrections. |
Implementation Example
Example: Caching Dynamic Components
Asynchronous Components Overview
In large applications, loading every component at once can lead to a massive initial JavaScript bundle, causing slow page loads. Asynchronous Components allow you to split your app into smaller chunks and load components from the server only when they are actually needed.
In Vue 3, this is achieved using the defineAsyncComponent function.
Implementation Approaches
| Method | Description | Use Case |
|---|---|---|
| Basic Usage | A simple function that returns a Promise (usually via a dynamic import()). |
Standard route-level or tab-level components. |
| Advanced Options | An object configuration that handles loading and error states. | Mission-critical components where user feedback during loading is required. |
| With Suspense | Integration with the <Suspense> built-in component. |
Coordinating multiple async dependencies (like data fetching + component loading). |
Basic vs. Advanced Syntax
1. Basic SyntaxThe most common way to lazy-load a component:
For a better user experience, you can manage the "waiting" period:
Key Benefits
- Code Splitting: Build tools like Vite or Webpack automatically recognize dynamic imports and create separate
.jsfiles for them. - Reduced Initial Load: Users only download the code for the page they are currently visiting.
- Improved Performance: Smaller entry bundles lead to faster "Time to Interactive" (TTI).
Using with <Suspense>
Vue 3's <Suspense> component can coordinate the loading state of an entire tree of async components. It provides "slots" for the ready state and the loading (fallback) state.
Provide and Inject Overview
Provide and Inject is a feature in Vue used for dependency injection. It allows a "grandparent" or ancestor component to serve as a dependency provider for all its descendants, regardless of how deep the component hierarchy is.
This solves the problem of "Prop Drilling," where data must be passed through multiple layers of intermediate components that don't actually need the data themselves.
How It Works
| Action | Component | Macro / Function | Description |
|---|---|---|---|
| Provide | Ancestor | provide(key, value) |
Defines the data or methods to be shared with descendants. |
| Inject | Descendant | inject(key, default?) |
Retrieves the data provided by an ancestor using the unique key. |
Provide/Inject vs. Props
| Feature | Props | Provide / Inject |
|---|---|---|
| Data Flow | Direct (Parent to Child). | Long-distance (Ancestor to any Descendant). |
| Coupling | High: Components must be explicitly linked. | Low: Intermediate components are unaware of the data. |
| Scalability | Becomes messy with deep nesting (Prop Drilling). | Clean for global-like state within a specific tree. |
| Visibility | Explicit in the template. | Implicit; can be harder to track where data originates. |
Implementation Example
The Ancestor ComponentBest Practices
- Reactivity: To keep the injected data reactive, ensure you provide a
reforreactiveobject. - Symbol Keys: For large applications, use Symbols as keys instead of strings to avoid naming collisions between different plugins or components.
- Mutations: If a child needs to change the data, it is best practice to provide a function (like
toggleTheme) that performs the mutation in the ancestor, rather than letting the child mutate state directly. - Plugin Usage: This pattern is the primary way Vue plugins (like Vue Router or Pinia) make their instances available to your entire app.
Component Registration Overview
In Vue.js, you must register components so that Vue knows how to locate and render them when they are used in a template. There are two primary ways to do this: Global and Local registration.
Comparison Table
| Feature | Global Registration | Local Registration |
|---|---|---|
| Declaration | Registered on the app instance (app.component). |
Registered within a specific component file. |
| Availability | Available everywhere in the application. | Only available in the component that imports it. |
| Bundle Size | Heavier: Included in the main bundle even if unused. | Lighter: Supports tree-shaking; only loaded if needed. |
| Dependency Tracking | Harder: Difficult to see where a component is used. | Clear: Explicit imports make dependencies easy to trace. |
| Best For | Common UI elements (Buttons, Inputs, Icons). | Feature-specific or complex sub-components. |
Implementation Differences
1. Global RegistrationRegistered in the main entry file (usually main.js). Once registered, you do not need to import them again in any .vue file.
The component is imported and used only where it is needed. This is the recommended approach for most components.
Pros and Cons
- Pros: Extremely convenient for "building blocks" (Base components) used in almost every view.
- Cons: It creates a "Global Namespace" which can lead to name collisions. It also bloats the initial JavaScript file because build tools cannot remove unused global components.
- Pros: Keeps the relationship between components explicit. Improves performance through tree-shaking.
- Cons: Can feel repetitive if you have to import the same 5-10 "Base" components in every single file.
Dynamic Components Overview
In Vue.js, Dynamic Components allow you to switch between different components at the exact same mount point without changing the URL or using complex v-if/v-else-if chains. This is achieved using the built-in <component> element with the special is attribute.
Key Features
-
The
:isAttribute: This can accept either a string (the name of a registered component) or the actual component object itself. -
State Management: By default, when you switch away from a component, it is destroyed. To preserve its state (like form inputs or scroll position), wrap it in a
<KeepAlive>component. -
Reactivity: The component updates immediately whenever the value passed to
:ischanges.
Implementation Comparison
| Approach | Syntax Example | Best For |
|---|---|---|
| String-based | <component is="HomeView" /> |
Globally registered components or basic HTML tags. |
| Object-based | <component :is="CurrentComponent" /> |
Components imported via Composition API (<script setup>). |
| Conditional | v-if="type === 'A'" |
Switching between only 2 or 3 static options. |
Code Example (Composition API)
In this example, we use a shallow reference to store the component object to avoid unnecessary reactivity overhead on the component definition itself.
Common Use Cases
- Tabbed Interfaces: Switching between "Settings," "Profile," and "Security" views within a single container.
- Dashboards: Rendering different types of "widgets" based on user configuration.
- Page Builders: Rendering content blocks (e.g., Image, Text, Video) based on a JSON response from a CMS.
shallowRef() instead of ref(). Since component objects are complex, ref() tries to make every internal property reactive, which can lead to performance warnings and unnecessary CPU overhead.
Composables Overview
In Vue 3, a Composable is a function that leverages the Composition API to encapsulate and reuse stateful logic. Think of it as a way to "package" a specific feature (like tracking mouse coordinates or fetching data) so it can be dropped into any component.
By convention, composable names start with "use," such as useMouse, useFetch, or useAuth.
Composables vs. Mixins
Before Vue 3, Mixins were the primary way to share code; however, they had significant flaws that Composables solve:
| Feature | Mixins (Vue 2) | Composables (Vue 3) |
|---|---|---|
| Data Source | Implicit: Hard to tell which mixin a property came from. | Explicit: Values are returned and destructured in the component. |
| Naming Conflicts | High Risk: Multiple mixins can use the same property names, causing collisions. | No Risk: You can rename destructured variables (e.g., const { data: user } = useFetch()). |
| Reactivity | Less flexible; logic is spread across options (data, methods). | Built entirely on the Reactivity API (ref, computed). |
| Logic Organization | Scattered: Logic is split by option type. | Grouped: Related state and functions stay together. |
Standard Composable Structure
A composable typically follows this pattern:
- Define state using
reforreactive. - Define methods to modify that state.
- Use lifecycle hooks (like
onMounted) if needed. - Return the state and methods.
useCounter.js
Using a Composable in a Component
Why They Are Better
- Tree-shaking: If you don't use a specific function from a composable, modern build tools can remove it from the final bundle.
- Ease of Testing: Since composables are just functions, they can be tested in isolation more easily than components.
- SSR Compatibility: Composables are designed to work smoothly with Server-Side Rendering (SSR).
Lifecycle Hooks Overview in the Composition API
In the Composition API (used within the setup() function or <script setup>), lifecycle hooks are accessed as imported functions. These functions accept a callback that executes when the specific lifecycle event occurs.
Mapping Options API to Composition API
Most hooks have a direct equivalent, usually prefixed with "on". Note that beforeCreate and created do not have equivalents because the setup() function itself acts as these hooks.
| Options API Hook | Composition API Hook | Execution Timing |
|---|---|---|
beforeCreate / created |
Not needed | Code runs directly inside setup(). |
beforeMount |
onBeforeMount() |
Right before the component is added to the DOM. |
mounted |
onMounted() |
After the component is inserted into the DOM. |
beforeUpdate |
onBeforeUpdate() |
When data changes, but before the DOM re-renders. |
updated |
onUpdated() |
After the DOM has been patched with changes. |
beforeUnmount |
onBeforeUnmount() |
Right before the component is destroyed. |
unmounted |
onUnmounted() |
After the component and its listeners are removed. |
Implementation Example
To use a hook, you must import it from the 'vue' package. You can also call the same hook multiple times within one setup() to keep related logic together.
Key Rules for Usage
- Synchronous Registration: Hooks must be called synchronously during the execution of
setup(). You cannot call them inside anasyncfunction orsetTimeout. - Multiple Hooks: Unlike the Options API, you can have multiple
onMounted()calls, which is essential for organizing code within Composables. - Internal State: Hooks have access to the component's internal state (refs, computed properties) because they are defined within the same scope.
Specialized Hooks
In addition to standard hooks, Vue provides:
onActivated/onDeactivated: Used exclusively with components cached via<KeepAlive>.onErrorCaptured: Used to catch errors originating from any descendant component.
Overview of <script setup>
Introduced in Vue 3.2, <script setup> is a compile-time syntactic sugar for using the Composition API inside Single-File Components (SFCs). It is currently the industry-recommended standard for writing Vue components because it significantly reduces boilerplate and improves developer experience.
Key Benefits and Recommendations
| Feature | Standard <script> with setup() |
<script setup> |
|---|---|---|
| Boilerplate | High (requires export default and return). |
Low (everything is automatically exposed). |
| Variable Exposure | Must manually return variables to use in templates. | Top-level variables are automatically available. |
| Performance | Standard runtime overhead. | Better performance: Compiled into a more efficient render function. |
| Type Support | Good. | Superior: Better IDE type-inference for TypeScript users. |
| Component Usage | Must register components in a components object. |
Just import it and use it directly in the template. |
Why It Is Recommended
- Conciseness: You write significantly less code. You don't have to repeat variable names in a
returnblock. - Ergonomics: It feels more like writing standard JavaScript/TypeScript.
- Template Optimization: Because the compiler knows which variables are used in the template, it can generate more optimized code than the traditional
setup()function. - Better Integration with Macros: It allows the use of Compiler Macros like
defineProps()anddefineEmits(), which don't need to be imported.
Comparison Example
Traditional Composition API<script setup> Way
Compiler Macros
When using <script setup>, specialized functions called "macros" are used to handle component-level options without imports:
defineProps(): Declares props.defineEmits(): Declares events.defineExpose(): Explicitly limits which properties are accessible via a template ref (by default,<script setup>components are "closed").
Template Refs Overview
While Vue's data-driven approach handles most DOM updates, there are times when you need direct access to a DOM element (e.g., to focus an input, integrate a third-party library like D3.js, or measure an element's dimensions). Template Refs allow you to create a reference to a specific element or child component.
How to Implement Template Refs
In the Composition API, template refs are created by defining a ref with an initial value of null and then binding it to an element in the template using the ref attribute.
| Step | Action | Code Example |
|---|---|---|
| 1. Create Ref | Initialize a ref with null. |
const myInput = ref(null) |
| 2. Bind in Template | Add ref="myInput" to the element. |
<input ref="myInput" /> |
| 3. Access | Use the .value property. |
myInput.value.focus() |
myInput.value inside setup() directly (outside of a hook), it will be null.
Usage in Lifecycle Hooks
Because the DOM element doesn't exist until Vue renders it, you must use onMounted to interact with it.
Refs on Components vs. Elements
| Target | Result of ref.value |
|---|---|
| HTML Element | Returns the actual DOM node (e.g., HTMLInputElement). |
| Vue Component | Returns the Component Instance. |
A Note on defineExpose
When using <script setup>, components are private by default. If a parent uses a template ref to access a child component, it won't see any of the child's variables unless the child explicitly allows it using defineExpose:
Refs inside v-for
When ref is used inside a v-for loop, the resulting ref should be handled as an Array containing all the elements after the component is mounted. In Vue 3, this requires binding the ref to a function or using a reactive array.
Understanding toRefs
In Vue 3, toRefs is a utility function used to convert a reactive object into a plain object where each property is a ref pointing to the original property of the source object.
It is primarily used to solve the problem of losing reactivity during destructuring or when using the spread operator (...) on a reactive object.
The Problem: Destructuring reactive
When you use a reactive object and destructure it using standard JavaScript syntax, the resulting variables are disconnected from Vue's reactivity system. They become plain, static values.
| Action | Resulting State |
|---|---|
Normal Access (state.count) |
Reactive: UI updates automatically when the value changes. |
Destructured (const { count } = state) |
Static: The variable count is just a local copy; changes don't trigger UI updates. |
Using toRefs |
Reactive: Each property is wrapped in a ref, maintaining the link to the original state. |
Technical Breakdown
-
Why Reactivity Fails: The
reactivefunction uses JavaScript Proxies. When you destructure a proxy, you are performing a value assignment (e.g.,const count = state.count). If the value is a primitive (number, string, boolean), it is copied by value, and the Proxy is bypassed. -
How
toRefsFixes It: It creates a "proxy" object where every property is a getter/setter that points back to the original object. Because these properties arerefs, they maintain reactivity even when separated from the parent object.
Implementation Example
Common Use Cases
-
Returning from Composables: To allow users to destructure the returned state without losing reactivity.
Example:const { user, status } = useAuth() -
Destructuring Props: To keep props reactive inside
setup()when you don't want to use theprops.xxxprefix everywhere.
Quick Comparison: toRef vs toRefs
| Utility | Description | Typical Use |
|---|---|---|
toRef |
Creates a ref for a single property. | Linking one specific prop to a local ref. |
toRefs |
Converts all properties into refs. | Destructuring an entire reactive object/props. |
Pinia Overview
Pinia is the official state management library for Vue.js, designed to replace Vuex. It provides a central "store" to house data that needs to be shared across many components, such as user authentication info, shopping carts, or global settings.
While Vuex was the standard for years, Pinia was built specifically to leverage the Composition API and modern TypeScript features, making it the default choice for Vue 3.
Why Pinia is Preferred over Vuex
| Feature | Vuex (Legacy) | Pinia (Modern) |
|---|---|---|
| Complexity | High: Requires Mutations, Actions, and Getters. | Low: Actions act like methods; Mutations are removed. |
| TypeScript | Difficult: Requires complex manual typing. | Native: Automatic type inference and full TS support. |
| Boilerplate | High: Lots of code required to set up a store. | Minimal: Feels like writing a standard Composable. |
| Modularity | Single Store: Uses namespaced modules. | Multi-Store: Each store is independent and auto-bundled. |
| DevTools | Standard support. | Enhanced support with time-travel and better inspection. |
Core Differences in Logic
1. The Removal of Mutations
In Vuex, you could only change state via Mutations (synchronous). This added a layer of abstraction that was often redundant.
- Pinia Approach: You change state directly or within an Action. There are no mutations, simplifying the mental model.
2. Modular by Design
Vuex uses a single "Root Store" with nested "Modules," making it easy to create a monolithic mess.
- Pinia Approach: You define separate stores (e.g.,
useUserStore.js,useCartStore.js). You only import what you need, enabling better code-splitting.
Anatomy of a Pinia Store
A Pinia store consists of three main parts, mirroring the logic of a Vue component:
| Component Part | Pinia Part | Description |
|---|---|---|
ref() / reactive() |
State | The central source of truth (reactive properties). |
computed() |
Getters | Derived state based on the current state (cached). |
function() |
Actions | Methods that mutate state or handle async logic (API calls). |
Code Example (Setup Store)
Usage in a component:
When to Use Pinia?
- When state needs to be shared across multiple routes or unrelated components.
- When you need to persist data (like user preferences) across the session.
- When "Prop Drilling" (passing data through 5 layers of components) makes your code unreadable.
Defining a Pinia Store
In Pinia, there are two ways to define a store: Option Stores (similar to the Options API) and Setup Stores (similar to the Composition API). Both patterns organize the store into three fundamental parts: State, Getters, and Actions.
The Three Core Pillars
| Pillar | Equivalent | Purpose |
|---|---|---|
| State | data() |
The central source of truth. In Option stores, it must be a function returning the initial state. |
| Getters | computed() |
Values derived from the state. They are cached and only re-calculate when dependencies change. |
| Actions | methods() |
Functions used for business logic and side effects (API calls). Unlike Vuex, these can be async. |
1. Option Store Syntax
This syntax is structured as an object and is often preferred by those transitioning from Vuex or those who like a strictly separated structure.
2. Setup Store Syntax
This is the modern approach. It uses functions like ref() and computed() and feels exactly like writing a standard Composable.
Accessing the Store in a Component
You import the specific store "use" function and initialize it inside your component's setup phase.
Key Rules & Tips
- ID Requirement: Every store must have a unique ID (the first argument) so Pinia can connect it to the DevTools and internal registry.
- storeToRefs: If you destructure state or getters (e.g.,
const { count } = store), you must wrap the store instoreToRefs(store)to maintain reactivity. - Direct Mutations: While Pinia allows
store.count++, it is cleaner to keep logic inside Actions for better debugging and reusability.
Navigation Guards Overview
Navigation Guards are hooks provided by Vue Router that allow you to "guard" or control access to specific routes. They are primarily used to redirect users, cancel navigation, or perform data fetching before a page is rendered.
Think of them as middleware for your URLs. They are essential for security (e.g., protecting an Admin panel) and user experience (e.g., warning a user before they leave a half-filled form).
Types of Navigation Guards
| Category | Guard Name | Scope | Common Use Case |
|---|---|---|---|
| Global | beforeEach |
Every single route change. | Checking for Auth tokens/Login state. |
| Global | afterEach |
After navigation is confirmed. | Changing page titles or sending analytics. |
| Per-Route | beforeEnter |
Defined inside the route config. | Permissions for specific admin routes. |
| In-Component | onBeforeRouteLeave |
Inside a .vue component. |
Unsaved changes confirmation. |
The Global beforeEach Guard
This is the most frequently used guard. In Vue Router 4, we typically use return values instead of the old next() callback:
to: The target route object.from: The current route object.- Return
false: Cancels the navigation. - Return
{ name: 'Login' }: Redirects the user. - Return
undefined(or nothing): Validates the navigation.
Example: Authentication Guard
The Full Navigation Flow
When a navigation is triggered, the hooks run in this strict sequence:
beforeRouteLeave(in the component being left).beforeEach(Global).beforeRouteUpdate(if params changed but component is reused).beforeEnter(in the route config).beforeRouteEnter(in the new component).beforeResolve(Global - right before confirmation).afterEach(Global - navigation finished).
Key Features
- Route Meta Fields: You can add
meta: { requiresAuth: true }to routes, which guards check to make decisions. - Asynchronous: Guards can be
async, allowing you to wait for API responses (like session validation). - Composition API: Use
onBeforeRouteLeaveandonBeforeRouteUpdatedirectly inside<script setup>.
Example: Preventing Unsaved Changes
Dynamic Route Matching Overview
In Vue Router, Dynamic Route Matching allows you to map a single route pattern to multiple URLs. Instead of defining a separate route for every user or product, you use a param (prefixed with a colon :) to capture dynamic values from the URL.
When a user visits /user/123 or /user/abc, the same component is rendered, but the specific ID is made available to the component.
Implementation Steps
| Step | Action | Example / Code |
|---|---|---|
| 1. Define Route | Use a colon : before the param name. |
{ path: '/user/:id', component: UserProfile } |
| 2. Navigate | Pass the value in the URL. | <router-link to="/user/123">View User</router-link> |
| 3. Access Data | Use the useRoute hook. |
const id = route.params.id |
Accessing Params in Components
With the Composition API, you use the useRoute function to access the current route's state.
Key Considerations
1. Reacting to Param Changes
When navigating from /user/1 to /user/2, the component instance is reused. Because the component isn't destroyed and re-mounted, lifecycle hooks like onMounted will not fire again.
To handle this, you can:
- Watch the params: Use
watch(() => route.params.id, (newId) => { ... }). - Use a Key: Add
:key="$route.fullPath"to your<router-view />to force a full re-mount (though this may impact performance).
2. Matching Patterns
You can use Regex to fine-tune what a dynamic segment matches:
- Numeric only:
{ path: '/user/:id(\\d+)' } - Optional params:
{ path: '/user/:id?' } - Repeatable params:
{ path: '/files/:path+' }(matches/files/a/b/c)
Programmatic Navigation
You can also navigate to dynamic routes using the router.push method:
Summary of Route Parameters
| Feature | Syntax | Example URL | Result in route.params |
|---|---|---|---|
| Single Param | /:id |
/123 |
{ id: '123' } |
| Multiple Params | /:category/:id |
/books/45 |
{ category: 'books', id: '45' } |
| Catch-all (404) | /:pathMatch(.*)* |
/any/bad/url |
{ pathMatch: ['any', 'bad', 'url'] } |
Lazy Loading Routes Overview
Lazy Loading is a technique where you defer the loading of route-specific code until the user actually navigates to that route. Instead of downloading the entire application's JavaScript in one massive "vendor" bundle, the application is split into smaller chunks.
In Vue Router, this is implemented using dynamic imports.
How It Works
When you define a route with a standard import, that component is bundled into the initial load. When you use a dynamic import function, the build tool (like Vite or Webpack) automatically creates a separate .js file for that route.
| Feature | Standard Import | Lazy Loaded (Dynamic) |
|---|---|---|
| Syntax | import Home from './Home.vue' |
() => import('./Home.vue') |
| When Loaded | At application startup. | Only when the user clicks the link. |
| Initial Bundle Size | Larger (slower "Time to Interactive"). | Smaller (faster initial load). |
Implementation Example
Why It Is Good for Performance
- Faster Initial Load: By reducing the "Main Thread" work required to parse JavaScript when a user first lands on your site, the page becomes interactive much sooner.
- Reduced Data Usage: Users who only visit the landing page don't "pay" the data cost for complex parts of the app (like an Admin Dashboard) they might never see.
- Efficient Caching: If you update the code for just one page, the browser only needs to re-download that specific chunk.
Grouping Chunks (Advanced)
Sometimes you want to group several related routes into the same chunk (e.g., all "User Settings" pages). You can do this using "Magic Comments":
Summary of Benefits
- Lowers TTI (Time to Interactive).
- Improves Lighthouse/Core Web Vitals scores.
- Enables better scalability for enterprise-level apps.
404 Catch-all Routes Overview
In Vue Router, a 404 page (or "Not Found" page) is handled by defining a specific route at the end of your routes array that uses a custom regular expression to match any URL that hasn't been caught by previous definitions.
Since Vue Router matches routes in the order they are defined, this "Catch-all" route must always be the last entry in your configuration.
Implementation Syntax
In Vue Router 4 (Vue 3), the syntax uses a custom parameter with a regular expression (.*)*.
| Syntax Component | Meaning |
|---|---|
:pathMatch |
The name of the parameter where the "bad" URL will be stored. |
(.*) |
The regular expression telling Vue to match any character sequence. |
* |
Makes the parameter repeatable, allowing it to capture nested paths (e.g., /lost/deep/here). |
Code Example
1. Define the Route
2. The NotFound Component
You can even display the invalid path to the user using the pathMatch parameter.
Advanced Use Cases
-
Specific Sub-sections: You can catch 404s within a specific path. For example,
path: '/user/:pathMatch(.*)*'would catch any invalid URL starting with/user/. -
Programmatic 404s: Sometimes a route is valid (e.g.,
/user/123), but the data doesn't exist in your database. In this case, you can manually trigger a redirect to your 404 page:
Best Practices
- Placement: Always place the catch-all route at the very bottom. If you place it at the top, it will match every URL, and your actual pages will never load.
- Lazy Loading: Always lazy load the 404 component. Since most users (hopefully) won't see it, there's no need to include it in the initial bundle.
- Naming: Give it a name like
NotFoundso you can easily redirect to it from navigation guards.
Vite Overview
Vite (French for "fast," pronounced /veet/) is a modern build tool and development server created by Evan You (the creator of Vue). It was designed to address the performance bottlenecks of Webpack-based tools like Vue CLI, especially as projects grow larger.
As of Vue 3, Vite is the official recommendation for starting new projects, effectively replacing the Vue CLI.
Key Differences: Vite vs. Vue CLI (Webpack)
The fundamental difference lies in how they handle code during development.
| Feature | Vue CLI (Webpack) | Vite |
|---|---|---|
| Dev Server Start | Slow: Bundles the entire app before serving. | Instant: Serves source code via Native ESM. |
| Hot Module Replacement | Performance degrades as the project grows. | Constant speed: Only replaces the changed module. |
| Build Tooling | Uses Webpack. | Uses Rollup for highly optimized builds. |
| Dependency Pre-bundling | Done during the main bundling process. | Done once using esbuild (written in Go, 10–100x faster). |
| Configuration | Often complex (vue.config.js). |
Simpler and leaner (vite.config.js). |
Why is Vite so much faster?
1. Cold Starts- Webpack: Must crawl your entire dependency graph and build a complete bundle before the server can start. If you have 1,000 components, it processes 1,000 components first.
- Vite: Categorizes modules into "dependencies" and "source code." It serves your source code over Native ESM. The browser requests the specific file it needs via an
importstatement, and Vite serves it on demand.
- Webpack: When a file changes, it re-bundles the affected chunk. Even with caching, the re-bundling time increases with project size.
- Vite: Hot Module Replacement (HMR) is decoupled from the total number of modules. It simply invalidates the chain between the changed module and its closest HMR boundary.
Comparing the Build Process
While Vite is "unbundled" during development, it still bundles your code for production to ensure the best performance in the browser.
| Stage | Development | Production |
|---|---|---|
| Vite Engine | Native ESM + esbuild | Rollup |
| Result | Rapid startup, instant updates | Highly optimized, small static assets |
Migration Status
- Vue CLI: Now in maintenance mode. It still works, but no new major features are being added.
- Vite: The standard. It supports Vue, React, Svelte, and vanilla JS. It is the engine behind Nuxt 3.
Custom Directives Overview
While Vue provides a robust set of default directives (like v-model or v-show), Custom Directives allow you to write reusable logic that interacts directly with the DOM. They are best used when you need low-level DOM access that isn't easily handled by standard component logic.
By convention, if you name a directive vFocus, it is used in the template as v-focus.
The Directive Lifecycle Hooks
A directive object provides several "hooks" that trigger at different stages of an element's life. These are similar to component lifecycle hooks but specifically for the element the directive is attached to.
| Hook | Timing |
|---|---|
created |
Called before the element's attributes or event listeners are applied. |
beforeMount |
Called when the directive is first bound to the element, before it's in the DOM. |
mounted |
(Most Common) Called when the element is inserted into the parent DOM. |
beforeUpdate |
Called before the containing component's VNode is updated. |
updated |
Called after the containing component and its children have updated. |
beforeUnmount |
Called before the element is removed from the DOM. |
unmounted |
Called when the element is removed. |
Implementation Example: Auto-Focus
1. Local Registration (<script setup>)
In <script setup>, any camelCase variable that starts with v can be used as a directive.
2. Global Registration
Useful for utilities you want available across your entire application.
Directive Arguments and Values
Directives can accept values, arguments, and modifiers, just like v-on:click.stop. These are passed via the binding object.
| Feature | Syntax | Binding Property |
|---|---|---|
| Value | v-color="'blue'" |
binding.value (the string 'blue') |
| Argument | v-color:background |
binding.arg (the string 'background') |
| Modifiers | v-color.delay |
binding.modifiers (an object { delay: true }) |
Example: Dynamic Styling Directive
When to Use Custom Directives?
- Third-party DOM Libraries: Integrating libraries like Tooltip.js or Input masking.
- Low-level Animations: Triggering specific CSS transitions on scroll.
- UI Utilities: Auto-focusing, clicking outside to close a menu, or image lazy-loading.
Note: Always prefer Components or Composables first. Only use Custom Directives when you truly need to touch the raw HTML element.
Virtual DOM Overview
The Virtual DOM (VDOM) is a lightweight, JavaScript-based representation of the actual Document Object Model (DOM). It is essentially a "tree" of plain JavaScript objects (called VNodes) that mimic the structure of the real HTML tags you see in the browser.
Vue uses the Virtual DOM to optimize updates. Instead of re-rendering the entire page every time a piece of data changes, Vue updates the "draft" (Virtual DOM) first, finds the differences, and only applies the necessary changes to the real browser DOM.
How the Process Works
Vue's rendering pipeline follows three main steps:
- Render: The component's template is compiled into a "Render Function," which returns a Virtual DOM tree.
- Reconcile (Diffing): When state changes, a new Virtual DOM tree is created. Vue compares the new tree with the old one using a highly optimized Diffing Algorithm.
- Patch: Vue identifies exactly what changed (e.g., just one text node or one CSS class) and "patches" only those specific parts of the real DOM.
Why use a Virtual DOM?
| Feature | Real DOM | Virtual DOM |
|---|---|---|
| Speed | Slow: Touching the DOM is expensive for the browser. | Fast: Operations are just JS object manipulations. |
| Efficiency | Updates the whole element/tree. | Updates only the changed attributes or nodes. |
| State Tracking | Manual: You must track what to update. | Automated: Vue tracks dependencies and updates for you. |
Vue 3 Optimizations: The "Compiler-Informed" VDOM
One of the reasons Vue 3 is faster than other VDOM frameworks (like React) is that its compiler is "smart." During the build step, it analyzes your template and adds Patch Flags.
- Static Hoisting: Vue identifies parts of your template that never change and hoists them out of the render function so they are created only once.
- Patch Flags: Vue marks dynamic elements with a "flag" (e.g., "only text changes"). During diffing, Vue skips checks for things it knows won't change.
- Block Tree: Vue flattens the tree structure during diffing, so it only loops through dynamic nodes rather than the entire nested HTML structure.
The Lifecycle of a Change
- Trigger: A reactive variable (e.g.,
count.value++) changes. - Notify: The "Effect" associated with the component's render function is triggered.
- Virtual Update: A new VNode tree is generated.
- The Patch: Vue sees that only the
<span>text changed and executesel.textContent = newCount.
Nuxt.js Overview
Nuxt.js is an open-source framework built on top of Vue.js that provides an opinionated, "batteries-included" environment for building production-ready applications. While Vue.js is a library focused on the UI layer, Nuxt is a full-stack framework that handles routing, meta tags, state management, and most importantly, different rendering modes.
Nuxt vs. Standard Vue (Vite)
| Feature | Standard Vue (Client-Side) | Nuxt (Server-Side/Static) |
|---|---|---|
| Rendering | Client-Side Rendering (CSR) only. | SSR, SSG, or Hybrid Rendering. |
| SEO | Difficult: Search engines see a blank HTML file initially. | Excellent: Content is pre-rendered on the server. |
| Routing | Manual: You must configure vue-router. |
File-based: Routes are auto-generated from your folder structure. |
| Folder Structure | Flexible/Unstructured. | Opinionated: Specific folders for pages, components, server, etc. |
| Performance | Larger initial JS bundle; slower FCP. | Optimized: Faster "First Contentful Paint" (FCP). |
What is SSR (Server-Side Rendering)?
In a standard Vue app, the browser receives a nearly empty HTML file and a large JavaScript bundle. The browser then "builds" the page. In SSR, the Nuxt server executes the Vue code, generates the final HTML string, and sends a fully-formed page to the browser.
When to use SSR?
- SEO is Critical: If your site relies on Google, Bing, or social media previews (Twitter/Open Graph), SSR ensures robots can read your content instantly.
- Slow Devices/Networks: SSR offloads the heavy lifting of the first render to the server, making the site feel faster on low-end mobile devices.
- Content-Heavy Sites: Blogs, e-commerce stores, and news portals benefit most from SSR.
Key Nuxt Features
- Auto-Imports: You don't need to import
ref,computed, or your own components; Nuxt handles it automatically. - Server Engine (Nitro): Nuxt includes a powerful server-side engine that allows you to write API routes (like Express) directly inside your Vue project in a
server/directory. - SEO Utilities: Simple composables like
useHead()oruseSeoMeta()make managing page titles and descriptions effortless. - Nuxt Modules: A massive ecosystem of modules (Nuxt Auth, Nuxt Image, Tailwind, etc.) that can be added with a single line of configuration.
The Rendering Modes of Nuxt
| Mode | How it Works | Best For |
|---|---|---|
| SSR | Server generates HTML on every request. | Dynamic sites with user-specific data (Dashboards). |
| SSG | HTML is generated at "build time" as static files. | Documentation, Blogs, Portfolio sites. |
| Hybrid | Different rules for different routes. | Large sites where some pages are static and others are dynamic. |
Data Flow Overview
In Vue.js, the way data moves between the state and the UI determines how predictable and maintainable your application is. While Vue is famous for its Two-way Binding convenience, the underlying architecture for component communication is strictly One-way Data Flow.
Comparison Table
| Feature | One-way Data Flow | Two-way Binding |
|---|---|---|
| Direction | Source → View (Downwards only). | Source ↔ View (Bidirectional). |
| Mechanism | Props down, Events up. | v-model directive. |
| Predictability | High: You always know where the change started. | Lower: Changes in the UI automatically mutate the state. |
| Primary Use | Parent-to-Child component communication. | Form inputs (input, checkbox, select). |
| Control | High: Logic can intercept/validate data. | Low: Data is updated immediately on input. |
1. One-way Data Flow (The Rule)
In Vue, props are read-only. A child component should never mutate a prop directly. If the child needs to change the data, it must emit an event to the parent, and the parent updates the state.
- Logic: Data flows down (Props), Actions flow up (Events).
- Benefit: Prevents "spaghetti code" where child components accidentally change parent state, making debugging difficult.
2. Two-way Binding (The Convenience)
Two-way binding means that when the data changes in the JavaScript, the UI updates; conversely, when the user types in the UI, the JavaScript data updates automatically.
In Vue 3, v-model is actually syntactic sugar for one-way data flow:
- It passes a prop (modelValue).
- It listens for an event (update:modelValue).
Example: Manual vs. Two-way
Technical Breakdown of v-model
When you use v-model on a custom component, Vue expands it under the hood to maintain one-way flow principles while giving you the ease of two-way binding.
| Code | Expanded Equivalent |
|---|---|
<CustomInput v-model="email" /> |
<CustomInput :modelValue="email" @update:modelValue="email = $event" /> |
When to Use Which?
- Use Two-way Binding (
v-model): Inside forms and simple input components where the relationship between the UI and the variable is 1:1. - Use One-way Data Flow: For complex application architecture. If you find yourself passing data through many layers of components, stick to strict one-way flow (or a state management tool like Pinia) to keep the data path clear.
CSS Scoping in Single-File Components
In Vue, CSS Scoping ensures that the styles defined in a component do not "leak" out and affect the rest of the application. By default, CSS in a <style> block is global, but Vue provides built-in mechanisms to isolate styles to the current component.
Scoping Mechanisms
| Feature | Syntax | Behavior |
|---|---|---|
| Global Style | <style> |
Standard CSS; affects the entire app. |
| Scoped Style | <style scoped> |
Limits styles to the current component using data-attributes. |
| CSS Modules | <style module> |
Compiles classes into unique hashed strings (e.g., .title_hj3k). |
1. Scoped CSS (scoped)
When you add the scoped attribute, Vue uses PostCSS to transform your CSS. It adds a unique attribute (like data-v-f3f3eg) to all elements in the component and appends that attribute to your CSS selectors.
Rendered Output:
2. Deep Selectors (:deep())
Sometimes you need to modify the style of a child component from a parent. Because of scoping, a standard selector won't reach inside the child. Use the :deep() pseudo-class to "pierce" the scope.
3. Slotted Selectors (:slotted())
By default, styles in a parent component do not affect content passed into a <slot>. To target slotted content specifically, use :slotted().
4. Dynamic CSS (v-bind in CSS)
Vue 3 allows you to link CSS values directly to your JavaScript state using the v-bind() function inside the <style> block. This is a game-changer for dynamic themes or UI elements.
Best Practices
- Use Scoped by Default: Always start with
<style scoped>to avoid naming collisions. - Avoid Deep Selectors for Logic: Only use
:deep()for styling third-party libraries or shared UI components where you can't edit the source. - Use v-bind for Layouts: Instead of complex
:stylebindings in the template, usev-bind()in CSS to keep the template clean.
CSS Modules Overview
CSS Modules are an alternative to <style scoped> for styling components. While Scoped CSS uses data-attributes to isolate styles, CSS Modules transform your class names into unique hashes during the build process. This ensures that no two classes in your entire application can ever conflict, even if they share the same name.
In Vue, you enable this by adding the module attribute to your <style> block.
Scoped CSS vs. CSS Modules
| Feature | <style scoped> |
<style module> |
|---|---|---|
| Mechanism | Adds data-v-xxxx attributes. |
Replaces class names with hashed strings. |
| Usage | Standard HTML classes: class="title". |
JS Object mapping: :class="$style.title". |
| Specificity | Increases slightly (due to attribute). | No increase (remains a single class). |
| Flexibility | Easier to use for beginners. | Better for large-scale, complex projects. |
How It Works
When you use <style module>, Vue exposes a computed property called $style to the component. This object contains the mapping between your "human-readable" class names and the "machine-hashed" versions.
Advanced Usage
Named ModulesIf you have multiple style blocks, you can name them to keep your styles organized. This is helpful for separating "layout" styles from "theme" styles.
<script setup>
You can access the module classes inside your logic using the useCssModule hook. This is useful if you need to pass a class name to a third-party library or a child component as a string.
Why use CSS Modules?
- Zero Conflict Guaranteed: Because every class is hashed, it is impossible for a style in
Header.vueto accidentally overwrite a style inFooter.vue. - Explicit Mapping: It makes it very clear which classes are being applied to which elements through
:classbinding. - Performance: No runtime overhead for attribute matching; the browser just handles standard classes.
When to Choose Modules over Scoped?
- Scoped: Best for most standard Vue applications where simplicity is key.
- Modules: Best for Design Systems, component libraries, or very large enterprise applications where absolute isolation and programmatic access are required.
Vue Transitions and Animations Overview
Vue provides the <Transition> and <TransitionGroup> built-in components to handle enter/leave animations for elements or components. These components don't animate elements themselves; instead, they detect when an element is inserted or removed and apply specific CSS classes at the right moments.
How <Transition> Works
The most common use case is wrapping an element controlled by v-if or v-show. Vue automatically toggles six CSS classes during the transition lifecycle:
| Class Name | Description |
|---|---|
v-enter-from |
Starting state for enter. Added before element is inserted. |
v-enter-active |
Active state for enter. Applied during the entire animation. |
v-enter-to |
Ending state for enter. Removed when animation finishes. |
v-leave-from |
Starting state for leave. Applied when leave is triggered. |
v-leave-active |
Active state for leave. Applied during the entire leaving phase. |
v-leave-to |
Ending state for leave. Removed when animation finishes. |
Basic Implementation
1. The TemplateYou can name your transition to customize the class prefix. If you name it fade, the classes become .fade-enter-active, etc.
Key Features and Components
| Component | Purpose | Key Feature |
|---|---|---|
<Transition> |
Single element/component. | Supports v-if, v-show, and dynamic components. |
<TransitionGroup> |
List of elements (v-for). |
Supports FLIP animation for moving items in a list. |
mode attribute |
Controls timing. | out-in (wait for old to leave) or in-out. |
| JS Hooks | Fine-grained control. | @before-enter, @enter, @after-enter. |
Transitioning Lists with <TransitionGroup>
Unlike <Transition>, <TransitionGroup> renders an actual DOM element (default is none, but can be specified with tag="ul"). It also requires every child to have a unique :key.
- The Move Class: Use
.list-moveto apply transitions to items that are changing position (e.g., when an item is deleted and others slide into place).
JavaScript Hooks
If you want to use an animation library like GSAP or Anime.js, you can skip CSS classes and use JavaScript hooks instead:
Note: Setting :css="false" tells Vue to skip CSS detection, which improves performance for JS-only animations.
Testing Vue Components Overview
Testing in the Vue ecosystem is primarily handled by two tools: Vitest (the test runner) and Vue Test Utils (the library for mounting and interacting with components).
As of 2026, Vitest is the industry standard for Vue projects due to its speed and native integration with Vite.
The Core Toolset
| Tool | Role | Description |
|---|---|---|
| Vitest | The Engine | Runs the tests, provides globals like describe, it, and expect. |
| Vue Test Utils (VTU) | The Driver | The official library that "mounts" a .vue component in a simulated browser environment. |
| Happy-dom / JSDOM | The Environment | A JavaScript implementation of the DOM so tests can run in Node.js. |
Key Concepts: mount vs. shallowMount
When testing a component, you have two ways to render it:
mount(): Renders the component and all of its children. Best for integration tests where you want to ensure components work together.shallowMount(): Renders the component but stubs out all children. Best for isolated unit tests to ensure the logic of a single component is correct without side effects from children.
A Basic Component Test
Suppose we have a Counter.vue component. Here is how we would test its functionality:
Common Testing Patterns
1. Testing PropsYou can pass props directly during the mounting process to see if the component renders correctly.
You can verify if a component correctly emitted an event back to its parent.
When testing components that rely on global plugins, you must provide them in the global.plugins array.
Best Practices for Testing
- Test Behavior, Not Implementation: Don't test private methods or internal state variables. Instead, test what the user sees (HTML) and how they interact (Events).
- Use
data-testattributes: Instead of selecting by CSS classes (which change often), use custom attributes like<button data-test="submit-btn">. - Async/Await: Always
awaitany action that triggers a DOM update (liketrigger()orsetValue()) to ensure Vue has finished its rendering cycle before you make assertions.
Functional Components Overview
In Vue 3, a Functional Component is a component that has no internal state (no data or ref), no lifecycle hooks, and no instance (no this). It is essentially a plain JavaScript function that accepts props and context and returns a Virtual DOM node (VNode).
Functional vs. Stateful Components
| Feature | Stateful Component (.vue) |
Functional Component (Function) |
|---|---|---|
| State | Can have ref, reactive. |
Stateless. |
| Lifecycle | onMounted, onUpdated, etc. |
None. |
| Performance | Standard (highly optimized). | Slightly faster (zero instance overhead). |
| Instance | Has a this or internal instance. |
No instance; pure function. |
| Complexity | Best for most UI elements. | Best for simple, repetitive wrappers. |
Defining a Functional Component
In Vue 3, functional components are defined as simple functions. They are no longer defined via an object property { functional: true } like they were in Vue 2.
1. Using JavaScript/TypeScript
2. Using Single File Components (SFC)
You can still use the <template> block by adding the functional attribute, though this is less common in Vue 3 as the performance gap has significantly narrowed.
When to Use Functional Components?
While standard components are now fast enough that the difference is negligible for most apps, functional components are still useful for:
- Higher-Order Components (HOCs): Creating components that wrap others to inject logic or styles.
- Dynamic Layout Wrappers: Components that decide which tag to render (e.g., a
SmartLinkthat chooses between an<a>and a<router-link>). - Recursive Trees: Rendering complex, nested structures (like file folders) where thousands of instances are created.
The "Context" Argument
The second argument in a functional component, context, provides three main properties:
attrs: All attributes passed to the component that aren't defined as props.slots: The content passed into slots.emit: The ability to trigger events back to the parent.
Key Considerations
- No
<script setup>: You cannot use the<script setup>syntax for functional components; they are defined as standard functions. - Template Compilation: If you write them as functions, you must use the
h()function (Render Function), which requires a deeper understanding of the Virtual DOM.
Production Optimization Overview
Optimizing a Vue application involves reducing the bundle size, improving load times, and ensuring the code runs as efficiently as possible in the browser. In modern Vue 3 development, most of this is handled by Vite (using Rollup under the hood), but understanding the underlying mechanisms allows for fine-tuning.
Core Optimization Techniques
| Technique | Tool / Method | Benefit |
|---|---|---|
| Tree Shaking | Rollup / ES Modules | Removes unused code from dependencies. |
| Minification | esbuild / Terser | Shrinks file size by removing whitespace and renaming variables. |
| Code Splitting | () => import() |
Loads code only when needed (Lazy Loading). |
| Asset Compression | Gzip / Brotli | Compresses files for faster transfer over the network. |
| Production Build | npm run build |
Switches Vue into "Production Mode" (disables warnings/dev tools). |
1. Tree Shaking
Tree shaking relies on Static Analysis of ES Modules (import and export). If you import a large library like lodash but only use one function, a tree-shaking bundler will "shake off" the unused functions, excluding them from the final bundle.
- Vue-specific Tip: Vue 3 is designed to be highly tree-shakeable. If you don't use features like
<Transition>orv-model, the code for those features is not included in your final JS file.
2. Minification & Mangling
Vite uses esbuild by default for minification because it is significantly faster than traditional tools.
- Minification: Removes comments, line breaks, and extra spaces.
- Mangling: Shortens variable names (e.g.,
const userAuthenticated = truebecomesconst a=true).
3. Dependency Optimization
Large dependencies are often the biggest cause of "bundle bloat".
- Manual Chunking: You can tell Vite to split large libraries into their own files so they can be cached separately by the browser.
- CDN Usage: For very large, rarely changing libraries (like a 3D engine), you might load them via a CDN rather than bundling them.
4. Reducing Feature Flags
Vue 3 includes code to support the Options API and Vue DevTools. If you are using only the Composition API in production, you can disable these flags to save a few kilobytes.
5. Image and Asset Optimization
- WebP/Avif Conversion: Use modern formats for images.
- SVG Bundling: Use tools like
vite-svg-loaderto inline icons, reducing HTTP requests. - Lazy Loading Images: Use the native
<img loading="lazy">or Vue-specific directives.
Summary: The "Production Mode" Difference
When you run npm run build, Vue automatically:
- Strips Dev Warnings: All those "Vue Warning" messages in your console are removed to save space and performance.
- Optimizes Templates: The compiler performs more aggressive "Static Hoisting" and "Patch Flag" insertions.
- Removes Debugging Code: Logic used only by the Vue DevTools is purged.
Vue DevTools Overview
Vue DevTools is the essential browser extension (available for Chrome, Firefox, and Edge) used for debugging and inspecting Vue.js applications in real-time. It acts as a "window" into the internal state of your app, allowing you to see things that are otherwise invisible in the standard browser console.
As of Vue 3, the extension has been completely rebuilt to support the Composition API, Pinia, and Vue Router integrations.
Core Features and Tabs
| Tab / Feature | Purpose | Key Capability |
|---|---|---|
| Components | Inspect the Virtual DOM tree. | View and edit props, setup variables (ref, reactive), and computed values on the fly. |
| Timeline | Performance and Event tracking. | Record component renders, mouse events, and keyboard inputs to find "jank" or lag. |
| Pinia / Vuex | State Management inspection. | View the global state, time-travel through state changes, and manually trigger "Actions." |
| Routes | Navigation history. | See the current route, active params, and history of navigation guards. |
| Events | Custom event tracking. | Inspect $emit payloads sent from child to parent components. |
Key Debugging Capabilities
1. Live State Editing
You don't need to refresh your code to see how a component behaves with different data. You can click on a ref value in the DevTools and manually change it. The UI will instantly react as if the change happened in your code.
Hovering over a component in the DevTools tree highlights the corresponding elements in the physical browser window. This is invaluable for locating specific components in complex, nested layouts.
3. "Open in Editor"By configuring your local environment, you can click a button next to a component name in the DevTools, and your code editor (like VS Code) will automatically open that specific file.
4. Performance ProfilingThe Timeline tab allows you to see exactly how long a component takes to "patch" the DOM. This helps identify "heavy" components that might be re-rendering too frequently.
The "New" Vite Plugin: Vue DevTools 7.0+
There is a newer version of Vue DevTools that runs inside your browser window as a Vite plugin, rather than just a browser extension.
- Setup:
npm add -D vite-plugin-vue-devtools - Advantage: It works across all browsers (including mobile via remote debugging) and provides a more integrated experience, including an Asset inspector and Module graph.
Security and Production
By default, Vue DevTools only works in Development Mode.
- Production: The extension is disabled to prevent users from inspecting your app's internal logic or state.
- Forcing Enablement: If you absolutely must use it in production (for debugging a live bug), you can set the
__VUE_PROD_DEVTOOLS__flag totruein your build configuration, though this is generally discouraged.
Summary of Benefits
- Visualizes the Component Hierarchy.
- Speeds up Debugging by exposing internal state.
- Monitors Performance via the timeline.
- Debugs Global State (Pinia) with time-traveling.
Overview of Scaling Vue Applications
As a Vue application grows from a simple project to an enterprise-level platform, the focus shifts from "making it work" to maintainability, performance, and developer experience. Scaling requires a combination of architectural discipline and the right tooling.
Core Principles for Large-Scale Vue
| Category | Best Practice | Benefit |
|---|---|---|
| Architecture | Use a Feature-based Folder Structure. | Avoids "folder bloat" by grouping logic by business domain. |
| Logic | Maximize Composables. | Keeps components thin and promotes logic reuse across the app. |
| Type Safety | Use TypeScript strictly. | Prevents runtime errors and provides excellent IDE "Intellisense." |
| State | Normalize Pinia Store structure. | Prevents deeply nested, hard-to-track global state. |
| Performance | Implement Async Components. | Reduces the main bundle size through strategic code-splitting. |
1. Feature-Based Folder Structure
In small apps, we often use components/, views/, and store/. In large apps, this leads to hundreds of files in one folder; instead, group by Domain:
2. Strict Component Design
- Props vs. Store: Pass data via props for UI components (atoms/molecules). Use Pinia only for "Smart" components (pages/organisms) that truly need global state.
- Controlled Components: Favor "Stateless" UI components that emit events rather than components that manage their own complex internal state.
- Slots over Props: If a component needs to render complex HTML provided by the parent, use Slots instead of passing long strings or HTML through props.
3. Performance at Scale
- ShallowRef: For large datasets (like a list of 1,000 objects from an API) that don't need deep reactivity, use
shallowRef. This prevents Vue from making every nested property reactive, saving massive amounts of memory. - Component Teleportation: Use
<Teleport>to move modals and popups to the bottom of the<body>to avoid CSSz-indexand "overflow: hidden" conflicts in complex layouts. - Vite Plugins: Use
unplugin-vue-componentsto auto-import components, reducing the boilerplate in your<script>blocks.
4. Error Handling & Monitoring
- Global Error Handler: Use
app.config.errorHandlerto catch and log errors to a service like Sentry. - Boundary Components: Use the
onErrorCapturedlifecycle hook in parent components to display "Fallback UI" if a specific feature crashes without breaking the whole app.
5. Consistency (The "Rulebook")
- ESLint & Prettier: Strictly enforce a coding style (e.g., Vue's official "Essential" rules).
- Component Library: Use a tool like Storybook to document and test UI components in isolation from the main app.