Vue 3: Building Applications with Persistent Layouts and Route-Driven Content
Modern web applications frequently require a persistent user interface layout where certain elements, such as a header, sidebar, or footer, remain constant while the main content area updates based on user interaction or routing. Vue Router in Vue 3 provides a straightforward mechanism to achieve this through its <router-view /> comopnent, allowing for dynamic content deliveyr within a static application shell.
The core of this pattern involves defining a root layout component that hosts the static UI elements and a designated placeholder for dynamic content. Typically, App.vue serves this purpose, acting as the primary container for the entire application. Within this component, global navigation elements, such as an el-menu from Element Plus, can be placed alongside a <router-view /> component. The <router-view /> component acts as a slot where the content of the currently matched route will be rendered.
Main Application Layout (App.vue)
This example demonstrates a horizontal navigation menu that controls the main content area. The activePath reactive variable ensures the correct menu item is highlighted based on the current URL path.
<template>
<div class="application-wrapper">
<header class="app-header">
<img src="/path/to/your/logo.png" alt="Company Logo" class="app-logo" />
<nav class="main-navigation">
<el-menu
:default-active="activePath"
mode="horizontal"
background-color="#2c3e50"
text-color="#ecf0f1"
active-text-color="#42b983"
router
>
<el-menu-item index="/dashboard">Dashboard Overview</el-menu-item>
<el-menu-item index="/products">Product Catalog</el-menu-item>
<el-menu-item index="/customers">Customer Database</el-menu-item>
<el-menu-item index="/analytics">System Analytics</el-menu-item>
<el-menu-item index="/support">Support Center</el-menu-item>
<el-menu-item>
<a href="https://element-plus.org/" target="_blank" rel="noopener noreferrer">Element Plus Docs</a>
</el-menu-item>
</el-menu>
</nav>
</header>
<main class="content-area">
<router-view />
</main>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import { useRoute } from 'vue-router';
// Access the current route object
const currentRoute = useRoute();
// Initialize activePath based on the current route's path
const activePath = ref(currentRoute.path);
// Watch for changes in the route path and update activePath
watch(() => currentRoute.path, (newPath) => {
activePath.value = newPath;
});
</script>
<style scoped>
.application-wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.app-header {
background-color: #2c3e50;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
}
.app-logo {
height: 40px;
margin-right: 20px;
}
.main-navigation {
flex-grow: 1;
display: flex;
justify-content: flex-end; /* Align menu items to the right */
}
.el-menu--horizontal {
border-bottom: none; /* Remove default bottom border from Element Plus menu */
}
.content-area {
flex-grow: 1;
padding: 30px;
background-color: #f8f9fa;
}
</style>
Route Configuration (src/router/index.js)
The <router-view /> component renders the component associated with the current route. To define these associations, a route configuration file (e.g., src/router/index.js) is used to map URL paths to specific Vue components. Each route object specifies a path, a name, and the component to render.
import { createRouter, createWebHistory } from 'vue-router';
// Import your application views/components
import DashboardPage from '../views/DashboardPage.vue';
import ProductsPage from '../views/ProductsPage.vue';
import CustomersPage from '../views/CustomersPage.vue';
import AnalyticsPage from '../views/AnalyticsPage.vue';
import SupportPage from '../views/SupportPage.vue';
import NotFoundPage from '../views/NotFoundPage.vue';
// Define the application routes
const applicationRoutes = [
{
path: '/',
redirect: '/dashboard' // Redirect the root path to the dashboard
},
{
path: '/dashboard',
name: 'Dashboard',
component: DashboardPage,
meta: { title: 'Dashboard - App Admin' }
},
{
path: '/products',
name: 'Products',
component: ProductsPage,
meta: { title: 'Products - App Admin' }
},
{
path: '/customers',
name: 'Customers',
component: CustomersPage,
meta: { title: 'Customers - App Admin' }
},
{
path: '/analytics',
name: 'Analytics',
component: AnalyticsPage,
meta: { title: 'Analytics - App Admin' }
},
{
path: '/support',
name: 'Support',
component: SupportPage,
meta: { title: 'Support - App Admin' }
},
{
// Catch-all route for any undefined paths
path: '/:catchAll(.*)',
name: 'NotFound',
component: NotFoundPage,
meta: { title: 'Page Not Found' }
}
];
// Create the router instance
const router = createRouter({
history: createWebHistory(), // Use HTML5 history mode
routes: applicationRoutes,
});
// Optional: Navigation guard to update the document title
router.beforeEach((to, from, next) => {
document.title = to.meta.title || 'App Admin Panel';
next();
});
export default router;
When a user clicks on an el-menu-item (which utilizes router-link functionality when the router prop is true on el-menu), Vue Router intercepts the navigation request. It then identifies the component mapped to the target path in the applicationRoutes configuration and renders that component within the <router-view /> slot in App.vue. This design ensures that the header and navigation menu remain consistently visible and functional across different application views, creating a cohesive user experience while only the content area updates.