Code splitting breaks your app into smaller JavaScript files (chunks) that load on demand instead of all at once. Lazy loading means loading a chunk only when the user actually needs it: navigating to a route, opening a modal, scrolling to a section. Vite handles this automatically when you use dynamic import().
Route-level code splitting
The most impactful split. Each route becomes its own chunk, loaded only when the user navigates to it:
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
},
{
path: '/settings',
component: () => import('./views/Settings.vue')
}
]Each () => import(...) tells Vite to create a separate chunk. The browser downloads /dashboard's code only when the user navigates there. This is the default pattern in Vue Router and requires no additional configuration.
Component-level code splitting
For heavy components within a page that aren't always needed:
<script setup>
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent({
loader: () => import('./components/HeavyChart.vue'),
loadingComponent: ChartSkeleton,
errorComponent: ChartError,
delay: 200,
timeout: 10000
})
</script>
<template>
<HeavyChart v-if="showChart" />
</template>delay prevents showing the loading component for fast loads (avoids a flash). timeout shows the error component if loading takes too long.
For simple cases without loading/error states:
const HeavyChart = defineAsyncComponent(
() => import('./components/HeavyChart.vue')
)Conditional lazy loading
Load components only when a condition is met:
<script setup>
import { defineAsyncComponent, shallowRef } from 'vue'
const AdminPanel = shallowRef(null)
async function loadAdmin() {
AdminPanel.value = defineAsyncComponent(
() => import('./components/AdminPanel.vue')
)
}
</script>
<template>
<button @click="loadAdmin">Open Admin</button>
<component :is="AdminPanel" v-if="AdminPanel" />
</template>Prefetching and preloading
Vite automatically adds <link rel="modulepreload"> for chunks linked from the entry point. For routes the user is likely to visit next, Vue Router's <RouterLink> doesn't prefetch by default, but you can trigger it manually:
function prefetchRoute(path: string) {
const route = router.resolve(path)
const components = route.matched.flatMap((r) =>
Object.values(r.components ?? {})
)
components.forEach((c) => {
if (typeof c === 'function') (c as Function)()
})
}In Nuxt, <NuxtLink> prefetches linked routes automatically when they enter the viewport.
Named chunks
In Webpack, magic comments group related routes into the same chunk:
const routes = [
{
path: '/settings/profile',
component: () =>
import(/* webpackChunkName: "settings" */ './views/SettingsProfile.vue')
},
{
path: '/settings/billing',
component: () =>
import(/* webpackChunkName: "settings" */ './views/SettingsBilling.vue')
}
]Vite uses Rollup, which does not support these magic comments. Use manualChunks in the Vite config instead:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
settings: [
'./src/views/SettingsProfile.vue',
'./src/views/SettingsBilling.vue'
]
}
}
}
}
})What Vite does automatically
| Feature | Automatic? |
|---|---|
Split on dynamic import() | Yes |
| Tree-shake unused exports | Yes |
| CSS code splitting (per-component) | Yes |
modulepreload for entry chunks | Yes |
| Vendor chunk separation | Yes (configurable) |
When to split
| Scenario | Approach |
|---|---|
| Different pages/routes | Route-level splitting (always do this) |
| Heavy component behind a toggle | defineAsyncComponent |
| Large library used on one page | Dynamic import() in the component |
| Admin section most users never visit | Separate route chunk |
| Components always visible on load | Don't split (adds latency) |
The biggest win is route-level splitting. It's the default in Vue Router and costs nothing to implement. Component-level splitting is for specific heavy components where the additional network request is worth the smaller initial bundle.
See also: What are async components? · How does Vue Router work? · What is Vite?
References
- Async Components - Vue.js docs
- Lazy Loading Routes - Vue Router docs
- Code Splitting - Vite docs