Skip to content
← Todas las preguntas
🔥 Avanzado

¿Cómo implementarías lazy loading y code splitting?

⚡ Rendimiento🧭 Vue Router⚡ Vite

El code splitting divide tu app en archivos JavaScript más pequeños (chunks) que se cargan bajo demanda en lugar de todos a la vez. El lazy loading significa cargar un chunk solo cuando el usuario realmente lo necesita: al navegar a una ruta, abrir un modal o hacer scroll a una sección. Vite gestiona esto automáticamente cuando usas import() dinámico.

Diagrama de bundle mostrando chunk principal y chunks async cargados bajo demanda

Code splitting a nivel de ruta

La división más impactante. Cada ruta se convierte en su propio chunk, cargado solo cuando el usuario navega a ella:

ts
const routes = [
  {
    path: '/',
    component: () => import('./views/Home.vue')
  },
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  },
  {
    path: '/settings',
    component: () => import('./views/Settings.vue')
  }
]

Cada () => import(...) le dice a Vite que cree un chunk separado. El navegador descarga el código de /dashboard solo cuando el usuario navega allí. Este es el patrón por defecto en Vue Router y no requiere configuración adicional.

Code splitting a nivel de componente

Para componentes pesados dentro de una página que no siempre se necesitan:

vue
<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>
Open in Vue Playground

delay evita mostrar el componente de carga en cargas rápidas (evita un flash). timeout muestra el componente de error si la carga tarda demasiado.

Para casos simples sin estados de carga/error:

ts
const HeavyChart = defineAsyncComponent(
  () => import('./components/HeavyChart.vue')
)

Lazy loading condicional

Cargar componentes solo cuando se cumple una condición:

vue
<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">Abrir Admin</button>
  <component :is="AdminPanel" v-if="AdminPanel" />
</template>
Open in Vue Playground

Prefetching y preloading

Vite añade automáticamente <link rel="modulepreload"> para los chunks enlazados desde el punto de entrada. Para rutas que el usuario probablemente visitará a continuación, el <RouterLink> de Vue Router no hace prefetch por defecto, pero puedes activarlo manualmente:

ts
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)()
  })
}

En Nuxt, <NuxtLink> hace prefetch de las rutas enlazadas automáticamente cuando entran en el viewport.

Chunks con nombre

En Webpack, los comentarios mágicos agrupan rutas relacionadas en el mismo chunk:

ts
const routes = [
  {
    path: '/settings/profile',
    component: () =>
      import(/* webpackChunkName: "settings" */ './views/SettingsProfile.vue')
  },
  {
    path: '/settings/billing',
    component: () =>
      import(/* webpackChunkName: "settings" */ './views/SettingsBilling.vue')
  }
]

Vite usa Rollup, que no soporta estos comentarios mágicos. Usa manualChunks en la configuración de Vite en su lugar:

ts
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          settings: [
            './src/views/SettingsProfile.vue',
            './src/views/SettingsBilling.vue'
          ]
        }
      }
    }
  }
})

Qué hace Vite automáticamente

Característica¿Automático?
Dividir en import() dinámicos
Tree-shake exports no usados
Code splitting CSS (por componente)
modulepreload para chunks de entrada
Separación de chunk de vendorsSí (configurable)

Cuándo dividir

EscenarioEnfoque
Diferentes páginas/rutasDivisión a nivel de ruta (hazlo siempre)
Componente pesado detrás de un toggledefineAsyncComponent
Librería grande usada en una sola páginaimport() dinámico en el componente
Sección de admin que la mayoría nunca visitaChunk de ruta separado
Componentes siempre visibles en la cargaNo dividir (añade latencia)

La mayor ganancia es la división a nivel de ruta. Es el patrón por defecto en Vue Router y no cuesta nada implementarlo. La división a nivel de componente es para componentes pesados específicos donde la petición de red adicional compensa un bundle inicial más pequeño.

Ver también: ¿Qué son los componentes asíncronos? · ¿Cómo funciona Vue Router? · ¿Qué es Vite?

Referencias

Publicado bajo la licencia MIT.