Lazy-Loading Routes in vue-element-admin: A Complete Implementation Strategy for Initial Bundle Optimization
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, 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:
// 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:
// 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 (lines 50-60) filters the master asyncRoutes array against the user's roles using the hasPermission helper:
// 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 (lines 42-44) injects these routes into the running router instance:
// 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 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:
// 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:
// 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:
- Initial Application Load – The router mounts with only
constantRoutes(login, 404, dashboard). These components are part of the initial bundle and render immediately. - Authentication Event – After login, the
permission.jsnavigation guard fetches user roles and dispatchespermission/generateRoutes. - Route Filtering – The
filterAsyncRoutesfunction walks theasyncRoutestree, comparing route metadata against user permissions viahasPermission. - Dynamic Registration –
router.addRoutes(accessRoutes)registers the filtered routes with Vue Router. No chunks download yet because the components are unresolved promise functions. - 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 insrc/router/modules/*.jscreates Webpack split points for every permission-controlled page. - Two-stage routing separates
constantRoutes(immediate) fromasyncRoutes(deferred), with injection happening post-authentication viarouter.addRoutesinsrc/permission.js. - Role-based filtering in
src/store/modules/permission.jsensures users receive only the route chunks they are authorized to access. - Webpack optimizations in
vue.config.jsdisable 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 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 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 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.
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 →