Skip to content

Deployment Strategies and Environment Management for Element Plus Applications

Overview

This guide covers comprehensive deployment strategies, environment management, and DevOps practices for Element Plus applications, including CI/CD pipelines, containerization, cloud deployment, and environment-specific configurations.

Environment Configuration Management

Environment Variables and Configuration

typescript
// src/config/environment.ts
export interface EnvironmentConfig {
  app: {
    name: string
    version: string
    environment: 'development' | 'staging' | 'production'
    debug: boolean
  }
  api: {
    baseUrl: string
    timeout: number
    retryAttempts: number
  }
  auth: {
    tokenKey: string
    refreshTokenKey: string
    tokenExpiry: number
  }
  monitoring: {
    enabled: boolean
    sentryDsn?: string
    googleAnalyticsId?: string
    hotjarId?: string
  }
  features: {
    enableBetaFeatures: boolean
    enableAnalytics: boolean
    enableErrorReporting: boolean
    enablePerformanceMonitoring: boolean
  }
  cdn: {
    baseUrl: string
    assetsPath: string
  }
  cache: {
    enabled: boolean
    ttl: number
    version: string
  }
}

class EnvironmentManager {
  private config: EnvironmentConfig
  
  constructor() {
    this.config = this.loadConfiguration()
    this.validateConfiguration()
  }
  
  private loadConfiguration(): EnvironmentConfig {
    const env = import.meta.env
    
    return {
      app: {
        name: env.VITE_APP_NAME || 'Element Plus App',
        version: env.VITE_APP_VERSION || '1.0.0',
        environment: env.VITE_APP_ENV || 'development',
        debug: env.VITE_APP_DEBUG === 'true'
      },
      api: {
        baseUrl: env.VITE_API_BASE_URL || 'http://localhost:3000/api',
        timeout: parseInt(env.VITE_API_TIMEOUT || '10000'),
        retryAttempts: parseInt(env.VITE_API_RETRY_ATTEMPTS || '3')
      },
      auth: {
        tokenKey: env.VITE_AUTH_TOKEN_KEY || 'auth_token',
        refreshTokenKey: env.VITE_AUTH_REFRESH_TOKEN_KEY || 'refresh_token',
        tokenExpiry: parseInt(env.VITE_AUTH_TOKEN_EXPIRY || '3600')
      },
      monitoring: {
        enabled: env.VITE_MONITORING_ENABLED === 'true',
        sentryDsn: env.VITE_SENTRY_DSN,
        googleAnalyticsId: env.VITE_GA_ID,
        hotjarId: env.VITE_HOTJAR_ID
      },
      features: {
        enableBetaFeatures: env.VITE_ENABLE_BETA_FEATURES === 'true',
        enableAnalytics: env.VITE_ENABLE_ANALYTICS === 'true',
        enableErrorReporting: env.VITE_ENABLE_ERROR_REPORTING === 'true',
        enablePerformanceMonitoring: env.VITE_ENABLE_PERFORMANCE_MONITORING === 'true'
      },
      cdn: {
        baseUrl: env.VITE_CDN_BASE_URL || '',
        assetsPath: env.VITE_CDN_ASSETS_PATH || '/assets'
      },
      cache: {
        enabled: env.VITE_CACHE_ENABLED === 'true',
        ttl: parseInt(env.VITE_CACHE_TTL || '3600'),
        version: env.VITE_CACHE_VERSION || '1.0.0'
      }
    }
  }
  
  private validateConfiguration() {
    const requiredVars = [
      'VITE_APP_NAME',
      'VITE_API_BASE_URL'
    ]
    
    const missing = requiredVars.filter(varName => !import.meta.env[varName])
    
    if (missing.length > 0) {
      throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
    }
    
    // Validate URLs
    try {
      new URL(this.config.api.baseUrl)
      if (this.config.cdn.baseUrl) {
        new URL(this.config.cdn.baseUrl)
      }
    } catch (error) {
      throw new Error('Invalid URL in configuration')
    }
  }
  
  public getConfig(): EnvironmentConfig {
    return { ...this.config }
  }
  
  public get(key: string): any {
    return this.getNestedValue(this.config, key)
  }
  
  private getNestedValue(obj: any, path: string): any {
    return path.split('.').reduce((current, key) => current?.[key], obj)
  }
  
  public isDevelopment(): boolean {
    return this.config.app.environment === 'development'
  }
  
  public isStaging(): boolean {
    return this.config.app.environment === 'staging'
  }
  
  public isProduction(): boolean {
    return this.config.app.environment === 'production'
  }
  
  public isFeatureEnabled(feature: keyof EnvironmentConfig['features']): boolean {
    return this.config.features[feature]
  }
}

export const environmentManager = new EnvironmentManager()
export const config = environmentManager.getConfig()

Environment-Specific Build Configuration

typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { createHtmlPlugin } from 'vite-plugin-html'
import { visualizer } from 'rollup-plugin-visualizer'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, process.cwd(), '')
  
  const isProduction = mode === 'production'
  const isStaging = mode === 'staging'
  const isDevelopment = mode === 'development'
  
  return {
    plugins: [
      vue(),
      
      // HTML template processing
      createHtmlPlugin({
        inject: {
          data: {
            title: env.VITE_APP_NAME || 'Element Plus App',
            description: env.VITE_APP_DESCRIPTION || 'Modern Vue 3 application with Element Plus',
            keywords: env.VITE_APP_KEYWORDS || 'vue,element-plus,typescript',
            author: env.VITE_APP_AUTHOR || 'Your Name',
            gaId: env.VITE_GA_ID || '',
            hotjarId: env.VITE_HOTJAR_ID || '',
            sentryDsn: env.VITE_SENTRY_DSN || ''
          }
        }
      }),
      
      // PWA configuration
      VitePWA({
        registerType: 'autoUpdate',
        workbox: {
          globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}']
        },
        manifest: {
          name: env.VITE_APP_NAME || 'Element Plus App',
          short_name: env.VITE_APP_SHORT_NAME || 'EP App',
          description: env.VITE_APP_DESCRIPTION || 'Modern Vue 3 application',
          theme_color: '#409eff',
          background_color: '#ffffff',
          display: 'standalone',
          icons: [
            {
              src: '/icons/icon-192x192.png',
              sizes: '192x192',
              type: 'image/png'
            },
            {
              src: '/icons/icon-512x512.png',
              sizes: '512x512',
              type: 'image/png'
            }
          ]
        }
      }),
      
      // Bundle analyzer for production builds
      isProduction && visualizer({
        filename: 'dist/stats.html',
        open: false,
        gzipSize: true,
        brotliSize: true
      })
    ].filter(Boolean),
    
    resolve: {
      alias: {
        '@': resolve(__dirname, 'src'),
        '~': resolve(__dirname, 'src'),
        '@components': resolve(__dirname, 'src/components'),
        '@views': resolve(__dirname, 'src/views'),
        '@utils': resolve(__dirname, 'src/utils'),
        '@assets': resolve(__dirname, 'src/assets'),
        '@stores': resolve(__dirname, 'src/stores')
      }
    },
    
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `
            @use "@/styles/variables.scss" as *;
            @use "@/styles/mixins.scss" as *;
          `
        }
      }
    },
    
    build: {
      target: 'es2015',
      outDir: 'dist',
      assetsDir: 'assets',
      sourcemap: !isProduction,
      minify: isProduction ? 'terser' : false,
      
      terserOptions: isProduction ? {
        compress: {
          drop_console: true,
          drop_debugger: true,
          pure_funcs: ['console.log', 'console.info']
        }
      } : undefined,
      
      rollupOptions: {
        output: {
          manualChunks: {
            // Vendor chunks
            vue: ['vue', 'vue-router', 'pinia'],
            'element-plus': ['element-plus'],
            
            // Utility chunks
            utils: ['lodash-es', 'dayjs', 'axios'],
            
            // Feature chunks
            charts: ['echarts', 'vue-echarts'],
            icons: ['@element-plus/icons-vue']
          },
          
          chunkFileNames: (chunkInfo) => {
            const facadeModuleId = chunkInfo.facadeModuleId
              ? chunkInfo.facadeModuleId.split('/').pop()
              : 'chunk'
            return `assets/js/[name]-[hash].js`
          },
          
          assetFileNames: (assetInfo) => {
            const info = assetInfo.name!.split('.')
            const ext = info[info.length - 1]
            
            if (/\.(png|jpe?g|gif|svg|webp|ico)$/i.test(assetInfo.name!)) {
              return `assets/images/[name]-[hash].${ext}`
            }
            
            if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name!)) {
              return `assets/fonts/[name]-[hash].${ext}`
            }
            
            if (/\.css$/i.test(assetInfo.name!)) {
              return `assets/css/[name]-[hash].${ext}`
            }
            
            return `assets/[name]-[hash].${ext}`
          }
        }
      },
      
      // Chunk size warnings
      chunkSizeWarningLimit: 1000
    },
    
    server: {
      host: '0.0.0.0',
      port: parseInt(env.VITE_DEV_PORT || '3000'),
      open: env.VITE_DEV_OPEN === 'true',
      cors: true,
      
      proxy: {
        '/api': {
          target: env.VITE_API_PROXY_TARGET || 'http://localhost:8080',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        }
      }
    },
    
    preview: {
      host: '0.0.0.0',
      port: parseInt(env.VITE_PREVIEW_PORT || '4173'),
      cors: true
    },
    
    define: {
      __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
      __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
      __COMMIT_HASH__: JSON.stringify(process.env.COMMIT_HASH || 'unknown')
    }
  }
})

Docker Containerization

Multi-stage Dockerfile

dockerfile
# Dockerfile
# Build stage
FROM node:18-alpine AS builder

# Set working directory
WORKDIR /app

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

# Install pnpm
RUN npm install -g pnpm

# Install dependencies
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build arguments
ARG NODE_ENV=production
ARG VITE_APP_ENV=production
ARG VITE_API_BASE_URL
ARG VITE_APP_NAME
ARG VITE_APP_VERSION

# Set environment variables
ENV NODE_ENV=$NODE_ENV
ENV VITE_APP_ENV=$VITE_APP_ENV
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
ENV VITE_APP_NAME=$VITE_APP_NAME
ENV VITE_APP_VERSION=$VITE_APP_VERSION

# Build application
RUN pnpm run build

# Production stage
FROM nginx:alpine AS production

# Install security updates
RUN apk update && apk upgrade && apk add --no-cache curl

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

# Copy built application
COPY --from=builder /app/dist /usr/share/nginx/html

# Copy environment configuration script
COPY docker/env-config.sh /docker-entrypoint.d/
RUN chmod +x /docker-entrypoint.d/env-config.sh

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

# Expose port
EXPOSE 80

# Start nginx
CMD ["nginx", "-g", "daemon off;"]

Nginx Configuration

nginx
# docker/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;
    
    # 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:; frame-ancestors 'self';" always;
    
    # Include server configurations
    include /etc/nginx/conf.d/*.conf;
}
nginx
# docker/default.conf
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;
    
    # Security
    server_tokens off;
    
    # Health check endpoint
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
    
    # Static assets with long cache
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Vary "Accept-Encoding";
        
        # CORS for fonts
        location ~* \.(woff|woff2|ttf|eot)$ {
            add_header Access-Control-Allow-Origin "*";
        }
    }
    
    # API proxy
    location /api/ {
        proxy_pass $API_BASE_URL;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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;
        proxy_cache_bypass $http_upgrade;
        
        # Timeouts
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
    
    # SPA fallback
    location / {
        try_files $uri $uri/ /index.html;
        
        # No cache for HTML files
        location ~* \.html$ {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
            add_header Pragma "no-cache";
        }
    }
    
    # Error pages
    error_page 404 /index.html;
    error_page 500 502 503 504 /50x.html;
    
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

Environment Configuration Script

bash
#!/bin/sh
# docker/env-config.sh

# Replace environment variables in nginx config
envsubst '${API_BASE_URL}' < /etc/nginx/conf.d/default.conf > /tmp/default.conf
mv /tmp/default.conf /etc/nginx/conf.d/default.conf

# Generate runtime configuration for the app
cat > /usr/share/nginx/html/config.js << EOF
window.__RUNTIME_CONFIG__ = {
  API_BASE_URL: '${API_BASE_URL:-http://localhost:3000/api}',
  APP_VERSION: '${APP_VERSION:-1.0.0}',
  ENVIRONMENT: '${ENVIRONMENT:-production}',
  SENTRY_DSN: '${SENTRY_DSN:-}',
  GA_ID: '${GA_ID:-}'
};
EOF

echo "Environment configuration completed"

Docker Compose for Different Environments

yaml
# docker-compose.yml (Development)
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - VITE_API_BASE_URL=http://localhost:8080/api
    depends_on:
      - api
    networks:
      - app-network
  
  api:
    image: your-api:latest
    ports:
      - "8080:8080"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
    depends_on:
      - db
    networks:
      - app-network
  
  db:
    image: postgres:14-alpine
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - app-network

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge
yaml
# docker-compose.prod.yml (Production)
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        - NODE_ENV=production
        - VITE_APP_ENV=production
        - VITE_API_BASE_URL=${API_BASE_URL}
        - VITE_APP_NAME=${APP_NAME}
        - VITE_APP_VERSION=${APP_VERSION}
    ports:
      - "80:80"
    environment:
      - API_BASE_URL=${API_BASE_URL}
      - APP_VERSION=${APP_VERSION}
      - ENVIRONMENT=production
      - SENTRY_DSN=${SENTRY_DSN}
      - GA_ID=${GA_ID}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - app-network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`yourdomain.com`)"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"

networks:
  app-network:
    external: true

CI/CD Pipeline

GitHub Actions Workflow

yaml
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop, staging]
  pull_request:
    branches: [main, develop]
  release:
    types: [published]

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

jobs:
  # Code Quality and Testing
  quality:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        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: Lint code
        run: pnpm run lint
      
      - name: Type check
        run: pnpm run type-check
      
      - name: Run 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
          flags: unittests
          name: codecov-umbrella
  
  # Security Scanning
  security:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'
  
  # Build and Test Docker Image
  build:
    needs: [quality, security]
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
      image-digest: ${{ steps.build.outputs.digest }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix={{branch}}-
      
      - name: Build and push Docker image
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            NODE_ENV=production
            VITE_APP_ENV=${{ github.ref_name == 'main' && 'production' || 'staging' }}
            VITE_APP_VERSION=${{ github.ref_name }}
            COMMIT_HASH=${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
  
  # Deploy to Staging
  deploy-staging:
    if: github.ref == 'refs/heads/develop'
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: |
          echo "Deploying to staging environment"
          # Add your staging deployment commands here
  
  # Deploy to Production
  deploy-production:
    if: github.ref == 'refs/heads/main'
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to production
        run: |
          echo "Deploying to production environment"
          # Add your production deployment commands here
  
  # Performance Testing
  performance:
    if: github.ref == 'refs/heads/main'
    needs: deploy-production
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v10
        with:
          configPath: './lighthouserc.json'
          uploadArtifacts: true
          temporaryPublicStorage: true

Kubernetes Deployment

yaml
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: element-plus-app
  labels:
    name: element-plus-app
---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: element-plus-app
data:
  API_BASE_URL: "https://api.yourdomain.com"
  APP_VERSION: "1.0.0"
  ENVIRONMENT: "production"
---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: element-plus-app
type: Opaque
data:
  SENTRY_DSN: <base64-encoded-sentry-dsn>
  GA_ID: <base64-encoded-ga-id>
---
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: element-plus-app
  namespace: 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: app
        image: ghcr.io/your-org/element-plus-app:latest
        ports:
        - containerPort: 80
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secrets
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        securityContext:
          allowPrivilegeEscalation: false
          runAsNonRoot: true
          runAsUser: 1000
          capabilities:
            drop:
            - ALL
      securityContext:
        fsGroup: 1000
---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: element-plus-app-service
  namespace: element-plus-app
spec:
  selector:
    app: element-plus-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: element-plus-app-ingress
  namespace: element-plus-app
  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:
    - yourdomain.com
    secretName: app-tls
  rules:
  - host: yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: element-plus-app-service
            port:
              number: 80
---
# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: element-plus-app-hpa
  namespace: element-plus-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: element-plus-app
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Cloud Deployment Strategies

AWS Deployment with CDK

typescript
// infrastructure/aws-stack.ts
import * as cdk from 'aws-cdk-lib'
import * as s3 from 'aws-cdk-lib/aws-s3'
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'
import * as route53 from 'aws-cdk-lib/aws-route53'
import * as targets from 'aws-cdk-lib/aws-route53-targets'
import * as acm from 'aws-cdk-lib/aws-certificatemanager'
import { Construct } from 'constructs'

export class ElementPlusAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)
    
    const domainName = 'yourdomain.com'
    const subdomain = 'app'
    const fullDomainName = `${subdomain}.${domainName}`
    
    // S3 bucket for static hosting
    const bucket = new s3.Bucket(this, 'AppBucket', {
      bucketName: `${subdomain}-${domainName}-${this.account}`,
      websiteIndexDocument: 'index.html',
      websiteErrorDocument: 'index.html',
      publicReadAccess: false,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true
    })
    
    // SSL Certificate
    const certificate = new acm.Certificate(this, 'AppCertificate', {
      domainName: fullDomainName,
      validation: acm.CertificateValidation.fromDns()
    })
    
    // CloudFront Origin Access Identity
    const oai = new cloudfront.OriginAccessIdentity(this, 'AppOAI')
    bucket.grantRead(oai)
    
    // CloudFront Distribution
    const distribution = new cloudfront.Distribution(this, 'AppDistribution', {
      defaultBehavior: {
        origin: new origins.S3Origin(bucket, { originAccessIdentity: oai }),
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
        compress: true
      },
      
      additionalBehaviors: {
        '/api/*': {
          origin: new origins.HttpOrigin('api.yourdomain.com'),
          viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
          cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
          originRequestPolicy: cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN
        },
        
        '/assets/*': {
          origin: new origins.S3Origin(bucket, { originAccessIdentity: oai }),
          viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
          cachePolicy: new cloudfront.CachePolicy(this, 'AssetsCachePolicy', {
            cachePolicyName: 'AssetsCache',
            defaultTtl: cdk.Duration.days(365),
            maxTtl: cdk.Duration.days(365),
            minTtl: cdk.Duration.days(365)
          })
        }
      },
      
      domainNames: [fullDomainName],
      certificate,
      
      errorResponses: [
        {
          httpStatus: 404,
          responseHttpStatus: 200,
          responsePagePath: '/index.html',
          ttl: cdk.Duration.minutes(5)
        },
        {
          httpStatus: 403,
          responseHttpStatus: 200,
          responsePagePath: '/index.html',
          ttl: cdk.Duration.minutes(5)
        }
      ],
      
      priceClass: cloudfront.PriceClass.PRICE_CLASS_100,
      enabled: true,
      comment: 'Element Plus App Distribution'
    })
    
    // Route 53 DNS
    const hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
      domainName
    })
    
    new route53.ARecord(this, 'AppARecord', {
      zone: hostedZone,
      recordName: subdomain,
      target: route53.RecordTarget.fromAlias(
        new targets.CloudFrontTarget(distribution)
      )
    })
    
    // Deploy static assets
    new s3deploy.BucketDeployment(this, 'AppDeployment', {
      sources: [s3deploy.Source.asset('./dist')],
      destinationBucket: bucket,
      distribution,
      distributionPaths: ['/*']
    })
    
    // Outputs
    new cdk.CfnOutput(this, 'BucketName', {
      value: bucket.bucketName,
      description: 'S3 Bucket Name'
    })
    
    new cdk.CfnOutput(this, 'DistributionId', {
      value: distribution.distributionId,
      description: 'CloudFront Distribution ID'
    })
    
    new cdk.CfnOutput(this, 'DomainName', {
      value: fullDomainName,
      description: 'Application Domain Name'
    })
  }
}

Vercel Deployment Configuration

json
// vercel.json
{
  "version": 2,
  "builds": [
    {
      "src": "package.json",
      "use": "@vercel/static-build",
      "config": {
        "distDir": "dist"
      }
    }
  ],
  "routes": [
    {
      "src": "/api/(.*)",
      "dest": "https://api.yourdomain.com/$1"
    },
    {
      "src": "/(.*\\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot))$",
      "headers": {
        "Cache-Control": "public, max-age=31536000, immutable"
      }
    },
    {
      "src": "/(.*)",
      "dest": "/index.html"
    }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        }
      ]
    }
  ],
  "env": {
    "VITE_APP_ENV": "production",
    "VITE_API_BASE_URL": "https://api.yourdomain.com"
  },
  "build": {
    "env": {
      "VITE_APP_ENV": "production"
    }
  }
}

Monitoring and Alerting in Production

typescript
// src/utils/production-monitoring.ts
export class ProductionMonitoring {
  private alertThresholds = {
    errorRate: 0.05, // 5%
    responseTime: 2000, // 2 seconds
    memoryUsage: 0.8, // 80%
    cpuUsage: 0.7 // 70%
  }
  
  constructor() {
    this.setupHealthChecks()
    this.setupPerformanceMonitoring()
    this.setupErrorTracking()
  }
  
  private setupHealthChecks() {
    // Expose health check endpoint
    if (typeof window !== 'undefined') {
      (window as any).__HEALTH_CHECK__ = () => {
        return {
          status: 'healthy',
          timestamp: new Date().toISOString(),
          version: import.meta.env.VITE_APP_VERSION,
          environment: import.meta.env.VITE_APP_ENV,
          uptime: performance.now(),
          memory: this.getMemoryUsage(),
          features: this.getFeatureStatus()
        }
      }
    }
  }
  
  private setupPerformanceMonitoring() {
    // Monitor Core Web Vitals
    this.monitorWebVitals()
    
    // Monitor resource loading
    this.monitorResourceLoading()
    
    // Monitor API performance
    this.monitorApiPerformance()
  }
  
  private setupErrorTracking() {
    // Track JavaScript errors
    window.addEventListener('error', (event) => {
      this.reportError({
        type: 'javascript_error',
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        stack: event.error?.stack
      })
    })
    
    // Track unhandled promise rejections
    window.addEventListener('unhandledrejection', (event) => {
      this.reportError({
        type: 'unhandled_promise_rejection',
        reason: event.reason
      })
    })
  }
  
  private monitorWebVitals() {
    // Implementation for Web Vitals monitoring
    // This would integrate with your monitoring service
  }
  
  private monitorResourceLoading() {
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        const resource = entry as PerformanceResourceTiming
        
        if (resource.duration > this.alertThresholds.responseTime) {
          this.sendAlert({
            type: 'slow_resource',
            resource: resource.name,
            duration: resource.duration,
            threshold: this.alertThresholds.responseTime
          })
        }
      })
    })
    
    observer.observe({ entryTypes: ['resource'] })
  }
  
  private monitorApiPerformance() {
    // Monitor API call performance and error rates
    // This would be integrated with your API client
  }
  
  private getMemoryUsage() {
    if ('memory' in performance) {
      const memory = (performance as any).memory
      return {
        used: memory.usedJSHeapSize,
        total: memory.totalJSHeapSize,
        limit: memory.jsHeapSizeLimit,
        percentage: memory.usedJSHeapSize / memory.jsHeapSizeLimit
      }
    }
    return null
  }
  
  private getFeatureStatus() {
    return {
      serviceWorker: 'serviceWorker' in navigator,
      webAssembly: 'WebAssembly' in window,
      intersectionObserver: 'IntersectionObserver' in window,
      performanceObserver: 'PerformanceObserver' in window
    }
  }
  
  private reportError(error: any) {
    // Send error to monitoring service
    fetch('/api/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...error,
        timestamp: new Date().toISOString(),
        userAgent: navigator.userAgent,
        url: window.location.href
      })
    }).catch(console.error)
  }
  
  private sendAlert(alert: any) {
    // Send alert to monitoring service
    fetch('/api/alerts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...alert,
        timestamp: new Date().toISOString(),
        severity: this.calculateSeverity(alert)
      })
    }).catch(console.error)
  }
  
  private calculateSeverity(alert: any): 'low' | 'medium' | 'high' | 'critical' {
    // Calculate alert severity based on type and thresholds
    switch (alert.type) {
      case 'slow_resource':
        return alert.duration > this.alertThresholds.responseTime * 2 ? 'high' : 'medium'
      case 'javascript_error':
        return 'high'
      case 'unhandled_promise_rejection':
        return 'critical'
      default:
        return 'low'
    }
  }
}

// Initialize production monitoring
if (import.meta.env.PROD) {
  new ProductionMonitoring()
}

This comprehensive deployment guide covers all aspects of deploying Element Plus applications to production, from environment management to monitoring and alerting.

Element Plus Study Guide