Build y despliegue a producción

Intermedio
Vuejs
Vuejs
Actualizado: 27/03/2026

Configuración de build en Vite

Vite genera un build optimizado para producción con minificación, tree-shaking y code splitting automático. La configuración del build se define en vite.config.ts dentro de la propiedad build:

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: 'dist',              // Directorio de salida
    minify: 'esbuild',          // Minificador: 'esbuild' (rápido) o 'terser' (más compresión)
    sourcemap: false,            // Generar sourcemaps (true, false, 'inline', 'hidden')
    target: 'es2020',           // Target de compilación
    cssCodeSplit: true,          // Separar CSS por chunk
    assetsInlineLimit: 4096,     // Inlinear assets menores a 4KB como base64
  },
})

Opciones de build relevantes

  • outDir: directorio donde se genera el build. Por defecto es dist
  • minify: 'esbuild' es más rápido, 'terser' produce bundles ligeramente más pequeños
  • sourcemap: true genera sourcemaps públicos, 'hidden' los genera pero no los referencia desde los archivos JS (útil para error tracking sin exponer el código fuente)
  • target: define el target de JavaScript para la transpilación. 'es2020' es adecuado para navegadores modernos
  • cssCodeSplit: cuando es true, el CSS de cada chunk se extrae en un archivo separado

Ejecutar el build en local

Desde la raíz del proyecto, el comando habitual es:

npm run build

Los archivos listos para producción se generan en el directorio dist/:

Variables de entorno

Vite utiliza archivos .env para gestionar variables de entorno. Los archivos se cargan según el modo de ejecución:

.env                  # Cargado siempre
.env.local            # Cargado siempre, ignorado por git
.env.development      # Cargado en modo development
.env.development.local # Cargado en modo development, ignorado por git
.env.production       # Cargado en modo production
.env.production.local  # Cargado en modo production, ignorado por git

Prefijo VITE_ para variables del cliente

Solo las variables con el prefijo VITE_ se exponen al código del cliente. Las variables sin este prefijo solo están disponibles en el entorno de build (Node.js):

# .env
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=Mi aplicación
SECRET_KEY=esto-no-se-expone-al-cliente

Acceso a variables de entorno

En el código de la aplicación, las variables se acceden con import.meta.env:

<script setup lang="ts">
const apiUrl = import.meta.env.VITE_API_URL
const appTitle = import.meta.env.VITE_APP_TITLE
const mode = import.meta.env.MODE         // 'development' o 'production'
const isDev = import.meta.env.DEV          // true en desarrollo
const isProd = import.meta.env.PROD        // true en producción
const baseUrl = import.meta.env.BASE_URL   // base URL configurada
</script>

<template>
  <header>
    <h1>{{ appTitle }}</h1>
    <span v-if="isDev" class="badge-dev">DEV</span>
  </header>
</template>

Tipado de variables de entorno

Para obtener autocompletado en TypeScript, se crea un archivo de declaración:

// env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_APP_TITLE: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

Modos personalizados

Además de development y production, se pueden crear modos personalizados con archivos .env específicos:

# .env.staging
VITE_API_URL=https://staging-api.example.com
VITE_APP_TITLE=Mi App (Staging)
# Build con modo staging
npx vite build --mode staging

Estrategias de chunk splitting

Vite divide automáticamente el código en chunks, pero se puede personalizar la estrategia para optimizar la caché del navegador:

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Agrupar librerías de Vue en un chunk separado
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          // Agrupar librerías UI
          'ui-vendor': ['@headlessui/vue', '@heroicons/vue'],
        },
      },
    },
  },
})

Una estrategia alternativa es dividir por patrón de ruta de módulo:

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            // Crear un chunk por cada paquete principal
            const packageName = id.split('node_modules/')[1].split('/')[0]
            if (['vue', 'vue-router', 'pinia'].includes(packageName)) {
              return 'vue-vendor'
            }
            return 'vendor'
          }
        },
      },
    },
  },
})

El beneficio de separar vendor chunks es que las librerías externas cambian con poca frecuencia, por lo que el navegador puede mantenerlas en caché incluso cuando se actualiza el código de la aplicación.

Static asset handling

Directorio public

Los archivos en el directorio public/ se sirven tal cual en la raíz del sitio, sin procesamiento. Es adecuado para favicon, robots.txt, archivos de configuración y assets que necesitan mantener su nombre exacto:

public/
  favicon.ico
  robots.txt
  og-image.png

Se referencian con rutas absolutas desde la raíz:

<template>
  <img src="/og-image.png" alt="Open Graph" />
</template>

Importación de assets

Los assets importados directamente en el código se procesan por Vite, reciben un hash en el nombre y se optimizan:

<script setup lang="ts">
import logoUrl from '@/assets/logo.png'
</script>

<template>
  <img :src="logoUrl" alt="Logo" />
</template>

Para generar URLs de forma dinámica:

function getImageUrl(name: string): string {
  return new URL(`../assets/images/${name}`, import.meta.url).href
}

Despliegue en plataformas estáticas

Una aplicación Vue generada con npm run build produce archivos estáticos en dist/ que se pueden servir desde cualquier servidor de archivos estáticos.

Netlify

Crear un archivo netlify.toml en la raíz del proyecto:

[build]
  command = "npm run build"
  publish = "dist"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

La regla de redirect es necesaria para que el enrutamiento HTML5 History de Vue Router funcione correctamente.

Vercel

Crear un archivo vercel.json en la raíz:

{
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

GitHub Pages

Configurar la base URL en vite.config.ts para que coincida con el nombre del repositorio:

export default defineConfig({
  base: '/nombre-del-repo/',
  // ...
})

Despliegue con Docker

Para entornos de producción con contenedores, se recomienda un Dockerfile multi-stage que separe la fase de build de la fase de servicio:

# Fase 1: Build
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Fase 2: Servir con nginx
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Configuración de nginx para SPA

El archivo nginx.conf debe configurar el fallback a index.html para que Vue Router gestione todas las rutas:

server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # Compresión gzip
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    gzip_min_length 256;

    # Caché para assets estáticos con hash
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Fallback a index.html para rutas SPA
    location / {
        try_files $uri $uri/ /index.html;
    }
}

Comandos para construir y ejecutar el contenedor:

docker build -t mi-app-vue .
docker run -p 8080:80 mi-app-vue

Sourcemaps en producción

Los sourcemaps permiten depurar el código original cuando se detectan errores en producción. La recomendación es usar sourcemap: 'hidden', que genera los archivos .map pero no incluye la referencia en los archivos JS:

export default defineConfig({
  build: {
    sourcemap: 'hidden',
  },
})

De esta forma, los sourcemaps se pueden subir a un servicio de error tracking sin exponerlos a los usuarios finales.

Performance monitoring

Lighthouse

Lighthouse es una herramienta de Google integrada en Chrome DevTools que audita el rendimiento, accesibilidad, SEO y buenas prácticas de una página web. Las métricas clave para una SPA Vue son:

  • First Contentful Paint (FCP): tiempo hasta que se pinta el primer contenido
  • Largest Contentful Paint (LCP): tiempo hasta que se pinta el contenido más grande
  • Cumulative Layout Shift (CLS): estabilidad visual durante la carga
  • Total Blocking Time (TBT): tiempo que el hilo principal está bloqueado

Core Web Vitals

Los Core Web Vitals se pueden medir programáticamente con la API web-vitals:

npm install web-vitals
// src/vitals.ts
import { onLCP, onFID, onCLS } from 'web-vitals'

function sendToAnalytics(metric: { name: string; value: number }) {
  console.log(`[${metric.name}]: ${metric.value}`)
  // Enviar a servicio de analítica
}

onLCP(sendToAnalytics)
onFID(sendToAnalytics)
onCLS(sendToAnalytics)

Integración con Sentry para error tracking

Sentry permite capturar y rastrear errores en producción con contexto detallado:

npm install @sentry/vue
// main.ts
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import * as Sentry from '@sentry/vue'
import App from './App.vue'

const app = createApp(App)

const router = createRouter({
  history: createWebHistory(),
  routes: [/* ... */],
})

Sentry.init({
  app,
  dsn: import.meta.env.VITE_SENTRY_DSN,
  integrations: [
    Sentry.browserTracingIntegration({ router }),
  ],
  tracesSampleRate: 0.2,
  environment: import.meta.env.MODE,
})

app.use(router)
app.mount('#app')

Sentry captura automáticamente errores no manejados, excepciones en componentes Vue y trazas de rendimiento de navegación. Los sourcemaps se pueden subir con @sentry/vite-plugin para obtener stack traces con el código original:

// vite.config.ts
import { sentryVitePlugin } from '@sentry/vite-plugin'

export default defineConfig({
  build: {
    sourcemap: 'hidden',
  },
  plugins: [
    vue(),
    sentryVitePlugin({
      org: 'mi-organizacion',
      project: 'mi-app-vue',
      authToken: process.env.SENTRY_AUTH_TOKEN,
    }),
  ],
})
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Vuejs es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Vuejs

Explora más contenido relacionado con Vuejs y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje