How the Dynamic Sidebar Generates Multi-Level Routes in vue-element-admin
The dynamic sidebar in vue-element-admin generates multi-level menus by recursively rendering Vuex-stored route arrays filtered by user roles, allowing runtime modifications through Vuex mutations or static configuration changes.
The vue-element-admin template provides a production-ready admin dashboard built with Vue 2 and Element UI. Its dynamic sidebar system creates navigation trees of unlimited depth by transforming router configurations into interactive menu components, automatically handling permission-based filtering and runtime updates.
How the Dynamic Sidebar Generates Multi-Level Routes
The sidebar construction follows a three-stage pipeline: loading filtered routes from Vuex, recursively rendering nested components, and dynamically generating the route list based on user permissions.
Loading Routes from Vuex
In src/layout/components/Sidebar/index.vue, the sidebar component accesses the permission_routes getter from the Vuex store. This getter returns the processed route array that has already been filtered according to the current user's roles.
<!-- src/layout/components/Sidebar/index.vue -->
<template>
<el-menu>
<sidebar-item
v-for="route in permission_routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</template>
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
export default {
components: { SidebarItem },
computed: {
...mapGetters([
'permission_routes'
])
}
}
</script>
The permission_routes getter, defined in src/store/getters.js, points to state.permission.routes, ensuring the sidebar always reflects the current permission state.
Recursive Rendering with SidebarItem
The multi-level capability is implemented in src/layout/components/Sidebar/SidebarItem.vue. This component checks for item.children and renders either a single menu item or a nested submenu that recursively calls itself for each child route.
<!-- src/layout/components/Sidebar/SidebarItem.vue -->
<template>
<div v-if="!item.hidden">
<!-- Single menu item -->
<el-menu-item v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)" :index="resolvePath(onlyOneChild.path)">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
<!-- Nested submenu -->
<el-submenu v-else :index="resolvePath(item.path)">
<template slot="title">
<item v-if="item.meta" :icon="item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
This recursive pattern allows the sidebar to support unlimited nesting levels, with each parent route rendering as an <el-submenu> containing nested <sidebar-item> components for its children.
Dynamic Route Generation and Filtering
The route list is generated dynamically after user authentication. The global navigation guard in src/permission.js triggers the generateRoutes action in the permission store module.
// src/permission.js
router.beforeEach(async(to, from, next) => {
// ... authentication checks
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// Get user info including roles
const { roles } = await store.dispatch('user/getInfo')
// Generate accessible routes based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// Dynamically add routes to router
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
} catch (error) {
// ... error handling
}
}
})
The generateRoutes action in src/store/modules/permission.js filters the async routes defined in src/router/index.js based on the user's roles, then commits the result to the Vuex state.
// src/store/modules/permission.js
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
The filterAsyncRoutes function recursively checks each route's meta.roles property against the current user's roles, ensuring only permitted routes appear in the sidebar.
Adding or Modifying Menu Items Dynamically
You can modify the sidebar menu either at compile-time by editing route definitions or at runtime by manipulating the Vuex state.
Static Compile-Time Modification
For permanent menu changes, edit the async route definitions in src/router/index.js or create modular route files in src/router/modules/.
// src/router/modules/nested.js
export default {
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
name: 'Nested',
meta: { title: 'Nested', icon: 'nested' },
children: [
{
path: 'menu1',
component: () => import('@/views/nested/menu1/index'),
name: 'Menu1',
meta: { title: 'Menu 1' },
children: [
{
path: 'menu1-1',
component: () => import('@/views/nested/menu1/menu1-1'),
name: 'Menu1-1',
meta: { title: 'Menu 1-1' }
},
{
path: 'menu1-2',
component: () => import('@/views/nested/menu1/menu1-2'),
name: 'Menu1-2',
meta: { title: 'Menu 1-2' }
}
]
}
]
}
Import this module in src/router/index.js and add it to the asyncRoutes array. The sidebar will automatically render the new multi-level structure on the next authentication cycle.
Runtime Modification via Vuex
To add menu items without reloading the page, commit new routes to the permission store and update the Vue Router instance.
// In any Vue component or utility function
import store from '@/store'
import router from '@/router'
import Layout from '@/layout'
const addDynamicMenu = () => {
const newRoute = {
path: '/dynamic',
component: Layout,
redirect: '/dynamic/page1',
name: 'Dynamic',
meta: { title: 'Dynamic Menu', icon: 'el-icon-star-on' },
children: [
{
path: 'page1',
component: () => import('@/views/dynamic/page1'),
name: 'DynamicPage1',
meta: { title: 'Page 1', icon: 'el-icon-document' }
},
{
path: 'page2',
component: () => import('@/views/dynamic/page2'),
name: 'DynamicPage2',
meta: { title: 'Page 2', icon: 'el-icon-printer' }
}
]
}
// Update Vuex state to trigger sidebar re-render
const currentRoutes = store.getters.permission_routes
store.commit('permission/SET_ROUTES', [...currentRoutes, newRoute])
// Add to Vue Router for navigation functionality
router.addRoutes([newRoute])
}
// Usage
addDynamicMenu()
This approach immediately updates the sidebar because Sidebar/index.vue reacts to changes in the permission_routes getter, which references the Vuex state mutated by SET_ROUTES.
Key Implementation Files
| File | Purpose |
|---|---|
src/layout/components/Sidebar/index.vue |
Entry component that maps permission_routes from Vuex and initiates the recursive rendering loop. |
src/layout/components/Sidebar/SidebarItem.vue |
Recursive component that renders el-submenu for parent routes and el-menu-item for leaf routes, supporting unlimited nesting. |
src/store/modules/permission.js |
Vuex module containing generateRoutes action, SET_ROUTES mutation, and filterAsyncRoutes function for role-based access control. |
src/permission.js |
Global navigation guard that triggers route generation after authentication and calls router.addRoutes to inject dynamic routes. |
src/router/index.js |
Defines constantRoutes and asyncRoutes; serves as the source of truth for all menu configurations. |
src/router/modules/*.js |
Modular route definitions (e.g., nested.js, components.js) that keep the router configuration organized and maintainable. |
src/store/getters.js |
Exposes permission_routes getter that the sidebar uses to access the current route list. |
Summary
- The dynamic sidebar in vue-element-admin generates multi-level menus by recursively rendering routes stored in the Vuex
permissionmodule, specifically through thepermission_routesgetter. - Multi-level nesting is achieved via the recursive
SidebarItem.vuecomponent, which renders<el-submenu>elements for routes with children and calls itself for each nested child route. - Route generation occurs in the
generateRoutesVuex action, which filtersasyncRoutesbased on user roles and commits the result viaSET_ROUTES, immediately updating the sidebar. - Adding menu items can be done statically by editing route definitions in
src/router/modules/or dynamically at runtime by committing new routes to the Vuex store and callingrouter.addRoutes.
Frequently Asked Questions
How does the sidebar handle unlimited menu nesting levels?
The sidebar handles unlimited nesting through recursive rendering in SidebarItem.vue. When a route has multiple visible children, the component renders an <el-submenu> and then calls itself (<sidebar-item>) for each child in the v-for loop. This recursive pattern continues for each level of the route tree, allowing the sidebar to display arbitrarily deep menu hierarchies without hardcoding depth limits.
What is the difference between constantRoutes and asyncRoutes?
constantRoutes are accessible to all users regardless of authentication status or role, typically including login, 404, and public pages. These routes are merged into the final route list without permission filtering. asyncRoutes are role-specific routes that require permission checking. The generateRoutes action filters this array based on the user's roles before adding them to the router and sidebar, ensuring users only see menu items they are authorized to access.
Can I add menu items without reloading the page after the user has logged in?
Yes, you can add menu items at runtime by manipulating the Vuex store and Vue Router. Dispatch a commit to permission/SET_ROUTES with the new route array containing your additional menu items, then call router.addRoutes() to register the new paths with the router. The sidebar will immediately display the new items because it reacts to changes in the permission_routes getter, which references the updated Vuex state.
Where should I define new static menu routes in the project?
Define new static routes in dedicated module files under src/router/modules/ (such as nested.js or components.js) to maintain organization. Export a route configuration object from the file, then import it into src/router/index.js and add it to the asyncRoutes array. This modular approach keeps the router configuration maintainable while allowing the permission system to filter and display your new multi-level menus in the sidebar.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →