# How the Dynamic Sidebar Generates Multi-Level Routes in vue-element-admin

> Learn how the dynamic sidebar in vue-element-admin creates multi-level routes. Discover how to add or modify menu items dynamically via Vuex or static config.

- Repository: [花裤衩/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
- Tags: deep-dive
- Published: 2026-02-27

---

**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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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.

```vue
<!-- 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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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.

```vue
<!-- 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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/permission.js) triggers the `generateRoutes` action in the permission store module.

```javascript
// 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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/permission.js) filters the async routes defined in [`src/router/index.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/router/index.js) based on the user's roles, then commits the result to the Vuex state.

```javascript
// 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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/router/index.js) or create modular route files in `src/router/modules/`.

```javascript
// 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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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.

```javascript
// 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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/permission.js) | Vuex module containing `generateRoutes` action, `SET_ROUTES` mutation, and `filterAsyncRoutes` function for role-based access control. |
| [`src/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/permission.js) | Global navigation guard that triggers route generation after authentication and calls `router.addRoutes` to inject dynamic routes. |
| [`src/router/index.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/nested.js), [`components.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/components.js)) that keep the router configuration organized and maintainable. |
| [`src/store/getters.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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 `permission` module, specifically through the `permission_routes` getter.
- **Multi-level nesting** is achieved via the recursive [`SidebarItem.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/SidebarItem.vue) component, which renders `<el-submenu>` elements for routes with children and calls itself for each nested child route.
- **Route generation** occurs in the `generateRoutes` Vuex action, which filters `asyncRoutes` based on user roles and commits the result via `SET_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 calling `router.addRoutes`.

## Frequently Asked Questions

### How does the sidebar handle unlimited menu nesting levels?

The sidebar handles unlimited nesting through recursive rendering in [`SidebarItem.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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`](https://github.com/PanJiaChen/vue-element-admin/blob/main/nested.js) or [`components.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/components.js)) to maintain organization. Export a route configuration object from the file, then import it into [`src/router/index.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/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.