Skip to content

Build Optimization and Deployment Strategies for Element Plus Applications

Overview

This guide covers comprehensive build optimization techniques and deployment strategies for Element Plus applications, focusing on performance, scalability, and production readiness.

Build Optimization Strategies

Bundle Analysis and Optimization

typescript
// vite.config.ts - Advanced build optimization
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { visualizer } from 'rollup-plugin-visualizer'
import { compression } from 'vite-plugin-compression'
import { createHtmlPlugin } from 'vite-plugin-html'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    vue(),
    
    // HTML optimization
    createHtmlPlugin({
      minify: true,
      inject: {
        data: {
          title: 'Element Plus App',
          description: 'Modern Vue 3 application with Element Plus'
        }
      }
    }),
    
    // Legacy browser support
    legacy({
      targets: ['defaults', 'not IE 11']
    }),
    
    // Gzip compression
    compression({
      algorithm: 'gzip',
      ext: '.gz'
    }),
    
    // Brotli compression
    compression({
      algorithm: 'brotliCompress',
      ext: '.br'
    }),
    
    // Bundle analyzer
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ],
  
  build: {
    target: 'es2015',
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false,
    
    // Chunk size warnings
    chunkSizeWarningLimit: 1000,
    
    rollupOptions: {
      output: {
        // Manual chunk splitting
        manualChunks: {
          // Vendor chunks
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'element-plus': ['element-plus'],
          'element-icons': ['@element-plus/icons-vue'],
          
          // Utility chunks
          'utils': ['lodash-es', 'dayjs', 'axios'],
          'charts': ['echarts', 'vue-echarts'],
          
          // Feature chunks
          'auth': [
            './src/stores/auth.ts',
            './src/composables/useAuth.ts',
            './src/api/auth.ts'
          ]
        },
        
        // Asset naming
        chunkFileNames: (chunkInfo) => {
          const facadeModuleId = chunkInfo.facadeModuleId
          if (facadeModuleId) {
            const fileName = facadeModuleId.split('/').pop()?.replace('.vue', '') || 'chunk'
            return `js/${fileName}-[hash].js`
          }
          return 'js/[name]-[hash].js'
        },
        
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name?.split('.') || []
          const ext = info[info.length - 1]
          
          if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico)$/i.test(assetInfo.name || '')) {
            return `images/[name]-[hash].${ext}`
          }
          
          if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name || '')) {
            return `fonts/[name]-[hash].${ext}`
          }
          
          if (ext === 'css') {
            return `css/[name]-[hash].${ext}`
          }
          
          return `assets/[name]-[hash].${ext}`
        }
      },
      
      // External dependencies (for CDN)
      external: process.env.NODE_ENV === 'production' ? [
        // 'vue',
        // 'vue-router',
        // 'element-plus'
      ] : []
    },
    
    // Minification
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.info'],
        passes: 2
      },
      mangle: {
        safari10: true
      },
      format: {
        comments: false
      }
    },
    
    // CSS optimization
    cssCodeSplit: true,
    cssMinify: true
  },
  
  // Dependency optimization
  optimizeDeps: {
    include: [
      'vue',
      'vue-router',
      'pinia',
      'element-plus/es',
      'element-plus/es/components/*/style/index',
      '@element-plus/icons-vue',
      'lodash-es',
      'dayjs'
    ],
    exclude: [
      'vue-demi'
    ]
  }
})

Tree Shaking Optimization

typescript
// src/plugins/element-plus.ts - Optimized Element Plus setup
import type { App } from 'vue'

// Import only required components
const elementPlusComponents = {
  // Form components
  ElForm: () => import('element-plus/es/components/form'),
  ElFormItem: () => import('element-plus/es/components/form-item'),
  ElInput: () => import('element-plus/es/components/input'),
  ElSelect: () => import('element-plus/es/components/select'),
  ElOption: () => import('element-plus/es/components/option'),
  ElButton: () => import('element-plus/es/components/button'),
  
  // Data display
  ElTable: () => import('element-plus/es/components/table'),
  ElTableColumn: () => import('element-plus/es/components/table-column'),
  ElPagination: () => import('element-plus/es/components/pagination'),
  ElCard: () => import('element-plus/es/components/card'),
  ElTag: () => import('element-plus/es/components/tag'),
  
  // Navigation
  ElMenu: () => import('element-plus/es/components/menu'),
  ElMenuItem: () => import('element-plus/es/components/menu-item'),
  ElSubmenu: () => import('element-plus/es/components/sub-menu'),
  ElBreadcrumb: () => import('element-plus/es/components/breadcrumb'),
  ElBreadcrumbItem: () => import('element-plus/es/components/breadcrumb-item'),
  
  // Feedback
  ElDialog: () => import('element-plus/es/components/dialog'),
  ElDrawer: () => import('element-plus/es/components/drawer'),
  ElPopover: () => import('element-plus/es/components/popover'),
  ElTooltip: () => import('element-plus/es/components/tooltip')
}

// Import only required icons
const elementPlusIcons = {
  Search: () => import('@element-plus/icons-vue/dist/components/Search'),
  Edit: () => import('@element-plus/icons-vue/dist/components/Edit'),
  Delete: () => import('@element-plus/icons-vue/dist/components/Delete'),
  Plus: () => import('@element-plus/icons-vue/dist/components/Plus'),
  Download: () => import('@element-plus/icons-vue/dist/components/Download'),
  Upload: () => import('@element-plus/icons-vue/dist/components/Upload'),
  User: () => import('@element-plus/icons-vue/dist/components/User'),
  Setting: () => import('@element-plus/icons-vue/dist/components/Setting')
}

// Lazy component registration
export async function setupElementPlus(app: App) {
  // Register components lazily
  for (const [name, importFn] of Object.entries(elementPlusComponents)) {
    app.component(name, defineAsyncComponent(importFn))
  }
  
  // Register icons lazily
  for (const [name, importFn] of Object.entries(elementPlusIcons)) {
    app.component(name, defineAsyncComponent(importFn))
  }
  
  // Import and setup global services
  const [ElMessage, ElMessageBox, ElNotification, ElLoading] = await Promise.all([
    import('element-plus/es/components/message'),
    import('element-plus/es/components/message-box'),
    import('element-plus/es/components/notification'),
    import('element-plus/es/components/loading')
  ])
  
  // Global properties
  app.config.globalProperties.$message = ElMessage.default
  app.config.globalProperties.$msgbox = ElMessageBox.default
  app.config.globalProperties.$notify = ElNotification.default
  app.config.globalProperties.$loading = ElLoading.default.service
}

Code Splitting Strategies

typescript
// src/router/index.ts - Advanced route-based code splitting
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'

// Lazy loading with error boundaries
const createAsyncComponent = (importFn: () => Promise<any>, chunkName?: string) => {
  return defineAsyncComponent({
    loader: importFn,
    loadingComponent: () => import('@/components/common/Loading.vue'),
    errorComponent: () => import('@/components/common/ErrorBoundary.vue'),
    delay: 200,
    timeout: 10000
  })
}

// Feature-based route grouping
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: createAsyncComponent(
      () => import(/* webpackChunkName: "home" */ '@/views/Home.vue'),
      'home'
    )
  },
  
  // Dashboard module
  {
    path: '/dashboard',
    component: createAsyncComponent(
      () => import(/* webpackChunkName: "dashboard" */ '@/layouts/DashboardLayout.vue'),
      'dashboard'
    ),
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "dashboard-home" */ '@/views/dashboard/Home.vue')
        )
      },
      {
        path: 'analytics',
        name: 'Analytics',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "dashboard-analytics" */ '@/views/dashboard/Analytics.vue')
        )
      },
      {
        path: 'reports',
        name: 'Reports',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "dashboard-reports" */ '@/views/dashboard/Reports.vue')
        )
      }
    ]
  },
  
  // Admin module (role-based loading)
  {
    path: '/admin',
    component: createAsyncComponent(
      () => import(/* webpackChunkName: "admin" */ '@/layouts/AdminLayout.vue'),
      'admin'
    ),
    meta: { requiresAuth: true, roles: ['admin'] },
    children: [
      {
        path: 'users',
        name: 'UserManagement',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "admin-users" */ '@/views/admin/Users.vue')
        )
      },
      {
        path: 'settings',
        name: 'SystemSettings',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "admin-settings" */ '@/views/admin/Settings.vue')
        )
      }
    ]
  }
]

// Dynamic route loading based on user permissions
export const loadUserRoutes = async (userRoles: string[]) => {
  const dynamicRoutes: RouteRecordRaw[] = []
  
  // Load admin routes only for admin users
  if (userRoles.includes('admin')) {
    const adminModule = await import(
      /* webpackChunkName: "admin-routes" */ './modules/admin'
    )
    dynamicRoutes.push(...adminModule.routes)
  }
  
  // Load manager routes for managers
  if (userRoles.includes('manager')) {
    const managerModule = await import(
      /* webpackChunkName: "manager-routes" */ './modules/manager'
    )
    dynamicRoutes.push(...managerModule.routes)
  }
  
  return dynamicRoutes
}

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

Asset Optimization

typescript
// scripts/optimize-assets.ts
import { readdir, readFile, writeFile, stat } from 'fs/promises'
import { join, extname } from 'path'
import sharp from 'sharp'
import { optimize } from 'svgo'

interface OptimizationOptions {
  inputDir: string
  outputDir: string
  quality: number
  formats: string[]
}

class AssetOptimizer {
  constructor(private options: OptimizationOptions) {}
  
  async optimizeImages() {
    const { inputDir, outputDir, quality, formats } = this.options
    
    const files = await this.getImageFiles(inputDir)
    
    for (const file of files) {
      const inputPath = join(inputDir, file)
      const ext = extname(file).toLowerCase()
      const baseName = file.replace(ext, '')
      
      if (['.jpg', '.jpeg', '.png'].includes(ext)) {
        // Generate multiple formats
        for (const format of formats) {
          const outputPath = join(outputDir, `${baseName}.${format}`)
          
          await sharp(inputPath)
            .resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })
            .toFormat(format as any, { quality })
            .toFile(outputPath)
          
          console.log(`✅ Optimized: ${file} -> ${baseName}.${format}`)
        }
        
        // Generate responsive images
        const sizes = [480, 768, 1024, 1920]
        for (const size of sizes) {
          const outputPath = join(outputDir, `${baseName}-${size}w.webp`)
          
          await sharp(inputPath)
            .resize(size, null, { withoutEnlargement: true })
            .webp({ quality })
            .toFile(outputPath)
        }
      } else if (ext === '.svg') {
        await this.optimizeSvg(inputPath, join(outputDir, file))
      }
    }
  }
  
  private async optimizeSvg(inputPath: string, outputPath: string) {
    const svgContent = await readFile(inputPath, 'utf-8')
    
    const result = optimize(svgContent, {
      plugins: [
        'preset-default',
        'removeDimensions',
        'removeViewBox',
        'cleanupIDs'
      ]
    })
    
    await writeFile(outputPath, result.data)
    console.log(`✅ Optimized SVG: ${inputPath}`)
  }
  
  private async getImageFiles(dir: string): Promise<string[]> {
    const files = await readdir(dir)
    const imageFiles: string[] = []
    
    for (const file of files) {
      const filePath = join(dir, file)
      const stats = await stat(filePath)
      
      if (stats.isFile()) {
        const ext = extname(file).toLowerCase()
        if (['.jpg', '.jpeg', '.png', '.svg', '.webp'].includes(ext)) {
          imageFiles.push(file)
        }
      }
    }
    
    return imageFiles
  }
}

// Usage
const optimizer = new AssetOptimizer({
  inputDir: 'src/assets/images',
  outputDir: 'dist/assets/images',
  quality: 80,
  formats: ['webp', 'avif']
})

optimizer.optimizeImages().catch(console.error)

Deployment Strategies

Multi-Environment Configuration

typescript
// config/environments.ts
export interface EnvironmentConfig {
  apiBaseUrl: string
  cdnUrl: string
  enableAnalytics: boolean
  enableErrorReporting: boolean
  logLevel: 'debug' | 'info' | 'warn' | 'error'
  features: {
    enablePWA: boolean
    enableOfflineMode: boolean
    enablePushNotifications: boolean
  }
}

const environments: Record<string, EnvironmentConfig> = {
  development: {
    apiBaseUrl: 'http://localhost:8080/api',
    cdnUrl: '',
    enableAnalytics: false,
    enableErrorReporting: false,
    logLevel: 'debug',
    features: {
      enablePWA: false,
      enableOfflineMode: false,
      enablePushNotifications: false
    }
  },
  
  staging: {
    apiBaseUrl: 'https://staging-api.example.com/api',
    cdnUrl: 'https://staging-cdn.example.com',
    enableAnalytics: true,
    enableErrorReporting: true,
    logLevel: 'info',
    features: {
      enablePWA: true,
      enableOfflineMode: true,
      enablePushNotifications: false
    }
  },
  
  production: {
    apiBaseUrl: 'https://api.example.com/api',
    cdnUrl: 'https://cdn.example.com',
    enableAnalytics: true,
    enableErrorReporting: true,
    logLevel: 'error',
    features: {
      enablePWA: true,
      enableOfflineMode: true,
      enablePushNotifications: true
    }
  }
}

export const getEnvironmentConfig = (): EnvironmentConfig => {
  const env = import.meta.env.MODE || 'development'
  return environments[env] || environments.development
}

Docker Deployment

dockerfile
# Dockerfile.production
# Multi-stage build for production
FROM node:18-alpine as build-stage

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./
COPY pnpm-lock.yaml ./

# Install pnpm and dependencies
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build application
ARG BUILD_ENV=production
ENV NODE_ENV=production
RUN pnpm run build:${BUILD_ENV}

# Production stage
FROM nginx:alpine as production-stage

# Install security updates
RUN apk update && apk upgrade

# Copy built assets
COPY --from=build-stage /app/dist /usr/share/nginx/html

# Copy nginx configuration
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY nginx/default.conf /etc/nginx/conf.d/default.conf

# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost/ || exit 1

# Create non-root user
RUN addgroup -g 1001 -S nginx && \
    adduser -S -D -H -u 1001 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx

# Set ownership
RUN chown -R nginx:nginx /usr/share/nginx/html
RUN chown -R nginx:nginx /var/cache/nginx

# Switch to non-root user
USER nginx

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
nginx
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    access_log /var/log/nginx/access.log main;
    
    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/atom+xml
        image/svg+xml;
    
    # Brotli compression (if module available)
    # brotli on;
    # brotli_comp_level 6;
    # brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self'; object-src 'none'; child-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self';" always;
    
    include /etc/nginx/conf.d/*.conf;
}
nginx
# nginx/default.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;
    
    # Enable HTTP/2 Server Push
    location = /index.html {
        add_header Link "</css/app.css>; rel=preload; as=style";
        add_header Link "</js/app.js>; rel=preload; as=script";
    }
    
    # Handle client-side routing
    location / {
        try_files $uri $uri/ /index.html;
        
        # Cache control for HTML files
        location ~* \.html$ {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
        }
    }
    
    # Static assets with long-term caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        
        # Enable CORS for fonts
        location ~* \.(woff|woff2|ttf|eot)$ {
            add_header Access-Control-Allow-Origin "*";
        }
    }
    
    # API proxy (if needed)
    location /api/ {
        proxy_pass http://backend:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Timeouts
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
    
    # Health check endpoint
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
    
    # Security
    location ~ /\. {
        deny all;
    }
    
    location ~ ~$ {
        deny all;
    }
}

Kubernetes Deployment

yaml
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: element-plus-app
  labels:
    app: element-plus-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: element-plus-app
  template:
    metadata:
      labels:
        app: element-plus-app
    spec:
      containers:
      - name: element-plus-app
        image: your-registry/element-plus-app:latest
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "50m"
          limits:
            memory: "128Mi"
            cpu: "100m"
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        env:
        - name: NODE_ENV
          value: "production"
---
apiVersion: v1
kind: Service
metadata:
  name: element-plus-app-service
spec:
  selector:
    app: element-plus-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: element-plus-app-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - your-domain.com
    secretName: element-plus-app-tls
  rules:
  - host: your-domain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: element-plus-app-service
            port:
              number: 80

CI/CD Pipeline

yaml
# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '18'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'pnpm'
    
    - name: Install pnpm
      run: npm install -g pnpm
    
    - name: Install dependencies
      run: pnpm install --frozen-lockfile
    
    - name: Run linting
      run: pnpm run lint
    
    - name: Run type checking
      run: pnpm run type-check
    
    - name: Run unit tests
      run: pnpm run test:unit
    
    - name: Run e2e tests
      run: pnpm run test:e2e
    
    - name: Upload coverage reports
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    
    outputs:
      image: ${{ steps.image.outputs.image }}
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'pnpm'
    
    - name: Install pnpm
      run: npm install -g pnpm
    
    - name: Install dependencies
      run: pnpm install --frozen-lockfile
    
    - name: Build application
      run: |
        if [[ ${{ github.ref }} == 'refs/heads/main' ]]; then
          pnpm run build:production
        else
          pnpm run build:staging
        fi
    
    - name: Log in to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=sha,prefix={{branch}}-
    
    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        file: ./Dockerfile.production
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
    
    - name: Output image
      id: image
      run: echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" >> $GITHUB_OUTPUT

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment: staging
    
    steps:
    - name: Deploy to staging
      run: |
        echo "Deploying ${{ needs.build.outputs.image }} to staging"
        # Add your staging deployment commands here

  deploy-production:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    
    steps:
    - name: Deploy to production
      run: |
        echo "Deploying ${{ needs.build.outputs.image }} to production"
        # Add your production deployment commands here

Performance Monitoring

typescript
// src/utils/performance.ts
export class PerformanceMonitor {
  private static instance: PerformanceMonitor
  private metrics: Map<string, number[]> = new Map()
  
  static getInstance(): PerformanceMonitor {
    if (!PerformanceMonitor.instance) {
      PerformanceMonitor.instance = new PerformanceMonitor()
    }
    return PerformanceMonitor.instance
  }
  
  // Measure Core Web Vitals
  measureCoreWebVitals() {
    // Largest Contentful Paint
    new PerformanceObserver((list) => {
      const entries = list.getEntries()
      const lastEntry = entries[entries.length - 1]
      this.recordMetric('LCP', lastEntry.startTime)
    }).observe({ entryTypes: ['largest-contentful-paint'] })
    
    // First Input Delay
    new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach((entry: any) => {
        this.recordMetric('FID', entry.processingStart - entry.startTime)
      })
    }).observe({ entryTypes: ['first-input'] })
    
    // Cumulative Layout Shift
    new PerformanceObserver((list) => {
      let clsValue = 0
      const entries = list.getEntries()
      
      entries.forEach((entry: any) => {
        if (!entry.hadRecentInput) {
          clsValue += entry.value
        }
      })
      
      this.recordMetric('CLS', clsValue)
    }).observe({ entryTypes: ['layout-shift'] })
  }
  
  // Measure custom metrics
  measureRouteChange(routeName: string) {
    const startTime = performance.now()
    
    return () => {
      const endTime = performance.now()
      const duration = endTime - startTime
      this.recordMetric(`route-${routeName}`, duration)
    }
  }
  
  measureComponentRender(componentName: string) {
    const startTime = performance.now()
    
    return () => {
      const endTime = performance.now()
      const duration = endTime - startTime
      this.recordMetric(`component-${componentName}`, duration)
    }
  }
  
  private recordMetric(name: string, value: number) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, [])
    }
    
    const values = this.metrics.get(name)!
    values.push(value)
    
    // Keep only last 100 measurements
    if (values.length > 100) {
      values.shift()
    }
    
    // Send to analytics service
    this.sendToAnalytics(name, value)
  }
  
  private sendToAnalytics(metric: string, value: number) {
    // Send to your analytics service
    if (typeof gtag !== 'undefined') {
      gtag('event', 'performance_metric', {
        metric_name: metric,
        metric_value: Math.round(value),
        custom_parameter: 'element_plus_app'
      })
    }
  }
  
  getMetrics() {
    const summary = new Map()
    
    for (const [name, values] of this.metrics) {
      const avg = values.reduce((a, b) => a + b, 0) / values.length
      const min = Math.min(...values)
      const max = Math.max(...values)
      
      summary.set(name, { avg, min, max, count: values.length })
    }
    
    return summary
  }
}

// Usage in main.ts
const monitor = PerformanceMonitor.getInstance()
monitor.measureCoreWebVitals()

This comprehensive guide provides advanced build optimization techniques and deployment strategies for Element Plus applications, ensuring optimal performance and scalable deployment across different environments.

Element Plus Study Guide