# Lazy-Loading Routes in vue-element-admin: A Complete Implementation Strategy for Initial Bundle Optimization

> Optimize initial bundle size in vue-element-admin with a complete lazy-loading routes implementation strategy. Learn how to defer component downloads until navigation.

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

---

**Vue-element-admin minimizes its initial JavaScript payload by splitting every route component into separate Webpack chunks and deferring their download until the user navigates to the specific page.**

The **PanJiaChen/vue-element-admin** repository implements a sophisticated **lazy-loading routes** architecture that combines dynamic `import()` syntax, role-based permission filtering, and aggressive Webpack chunk optimization. This strategy ensures that users download only the essential application shell on first load, while permission-protected pages arrive on-demand as the user explores the admin interface.

## Route-Level Code Splitting with Dynamic Imports

The foundation of the lazy-loading strategy lies in how route components are defined. Instead of static imports, every async route uses a dynamic import function that Webpack recognizes as a code-split point.

In [`src/router/index.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/router/index.js), the constant routes (immediate pages like login and dashboard) are imported statically, but the syntax for lazy-loaded components appears in the route modules. For example, line 49 demonstrates the pattern:

```javascript
// src/router/index.js (line 49)
component: () => import('@/views/redirect/index')

```

This arrow function returning `import('@/views/...')` tells Webpack to create a separate chunk file for that view. The same pattern appears throughout `src/router/modules/*.js`, where each feature module defines its routes with lazy-loaded components:

```javascript
// src/router/modules/components.js (excerpt)
{
  path: 'tinymce',
  component: () => import('@/views/components-demo/tinymce'), // lazy load point
  name: 'TinymceDemo',
  meta: { title: 'Tinymce', icon: 'edit' }
}

```

## Permission-Driven Async Route Injection

The application implements a two-stage routing system to control when lazy-loaded chunks are requested. First, the router initializes with only `constantRoutes`—the minimal pages required for login and public access. After authentication, the system dynamically injects role-specific routes.

The `generateRoutes` action in [`src/store/modules/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/permission.js) (lines 50-60) filters the master `asyncRoutes` array against the user's roles using the `hasPermission` helper:

```javascript
// src/store/modules/permission.js (lines 50-60 excerpt)
const generateRoutes = async(roles) => {
  const accessedRoutes = await filterAsyncRoutes(asyncRoutes, roles)
  commit('SET_ROUTES', accessedRoutes)
  return accessedRoutes
}

```

Once filtered, the navigation guard in [`src/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/permission.js) (lines 42-44) injects these routes into the running router instance:

```javascript
// src/permission.js (lines 42-44 excerpt)
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
router.addRoutes(accessRoutes)
next({ ...to, replace: true })

```

Because each route in `accessRoutes` uses the `() => import()` syntax, the corresponding Webpack chunks are not loaded during this injection—they are fetched later, only when the user actually navigates to the specific page.

## Webpack Configuration for Optimized Code Splitting

The [`vue.config.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/vue.config.js) file contains critical optimizations that support the lazy-loading strategy by preventing premature chunk downloads and organizing dependencies into cacheable bundles.

The configuration explicitly disables prefetching to avoid automatically loading all async chunks on startup, while keeping preload for initial assets only:

```javascript
// vue.config.js chainWebpack excerpt
chainWebpack(config) {
  // Disable prefetch to prevent loading all async chunks upfront
  config.plugins.delete('prefetch')
  
  // Keep preload for initial assets only
  config.plugin('preload').tap(() => [{ 
    rel: 'preload', 
    include: 'initial' 
  }])
}

```

Additionally, the `optimization.splitChunks` configuration (lines 96-118) organizes third-party libraries into separate cache groups:

```javascript
// vue.config.js optimization configuration
config.when(process.env.NODE_ENV !== 'development', cfg => {
  cfg.optimization.splitChunks({
    chunks: 'all',
    cacheGroups: {
      libs: { 
        name: 'chunk-libs', 
        test: /[\\/]node_modules[\\/]/, 
        priority: 10, 
        chunks: 'initial' 
      },
      elementUI: { 
        name: 'chunk-elementUI', 
        test: /[\\/]node_modules[\\/]_?element-ui(.*)/, 
        priority: 20 
      },
      commons: { 
        name: 'chunk-commons', 
        test: resolve('src/components'), 
        minChunks: 3, 
        priority: 5, 
        reuseExistingChunk: true 
      }
    }
  })
  cfg.optimization.runtimeChunk('single')
})

```

This setup extracts the runtime into its own file (`runtimeChunk: 'single'`) and isolates Element UI and common components into `chunk-elementUI` and `chunk-commons`, allowing the browser to cache these dependencies independently of application code changes.

## The Complete Lazy-Loading Execution Flow

The integration of these components creates a precise loading sequence that keeps the initial bundle under approximately 200KB gzipped:

1. **Initial Application Load** – The router mounts with only `constantRoutes` (login, 404, dashboard). These components are part of the initial bundle and render immediately.
2. **Authentication Event** – After login, the [`permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/permission.js) navigation guard fetches user roles and dispatches `permission/generateRoutes`.
3. **Route Filtering** – The `filterAsyncRoutes` function walks the `asyncRoutes` tree, comparing route metadata against user permissions via `hasPermission`.
4. **Dynamic Registration** – `router.addRoutes(accessRoutes)` registers the filtered routes with Vue Router. No chunks download yet because the components are unresolved promise functions.
5. **On-Demand Chunk Loading** – When the user clicks a navigation link to a protected page, Vue Router executes the component function, triggering the `import()` and fetching the specific Webpack chunk for that view only.

## Summary

- **Dynamic `import()` syntax** in `src/router/modules/*.js` creates Webpack split points for every permission-controlled page.
- **Two-stage routing** separates `constantRoutes` (immediate) from `asyncRoutes` (deferred), with injection happening post-authentication via `router.addRoutes` in [`src/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/permission.js).
- **Role-based filtering** in [`src/store/modules/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/permission.js) ensures users receive only the route chunks they are authorized to access.
- **Webpack optimizations** in [`vue.config.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/vue.config.js) disable prefetch, configure strategic chunking of libraries, and extract the runtime to minimize and cache the initial payload.

## Frequently Asked Questions

### How does vue-element-admin prevent all async chunks from loading on startup?

The project explicitly deletes the prefetch plugin in [`vue.config.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/vue.config.js) using `config.plugins.delete('prefetch')`. This prevents Webpack from generating `<link rel="prefetch">` tags for every async chunk, ensuring that only the initial bundle and explicitly preloaded assets are requested when the application first loads.

### What is the difference between constantRoutes and asyncRoutes in this implementation?

`constantRoutes` are defined in [`src/router/index.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/router/index.js) and contain pages like login and 404 that must be available immediately without authentication. These are bundled with the initial payload. `asyncRoutes` are defined in `src/router/modules/*.js` and contain permission-protected pages that use lazy-loaded components; they are filtered by role and injected via `router.addRoutes` only after the user logs in.

### Why does the implementation use `router.addRoutes` instead of defining all routes upfront?

The `router.addRoutes` method allows the application to defer both the registration and the physical loading of route components until after user permissions are known. This ensures that unauthorized users never download chunks for admin-only pages, and authorized users download specific chunks only when they navigate to those sections, significantly reducing memory and bandwidth usage on initial load.

### How are third-party libraries handled in the chunking strategy?

The [`vue.config.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/vue.config.js) file configures `optimization.splitChunks` with specific cache groups: `chunk-libs` for all node_modules, `chunk-elementUI` specifically for Element UI components, and `chunk-commons` for shared application components. This separates vendor code from application code, allowing browsers to cache heavy libraries independently and ensuring that updates to application logic do not force re-downloads of stable dependencies.