Skip to content

Deployment and Operations for Element Plus SSR

Overview

This guide covers comprehensive deployment strategies and operational practices for Element Plus server-side rendering applications, including containerization, orchestration, monitoring, and production best practices.

Docker Containerization

Multi-Stage Dockerfile

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

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 the application
RUN pnpm run build
RUN pnpm run build:ssr

# Production stage
FROM node:18-alpine AS production

# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init

# Create app user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

WORKDIR /app

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

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

# Copy built application from builder stage
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/dist-ssr ./dist-ssr
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

# Copy server files
COPY --chown=nextjs:nodejs server ./server
COPY --chown=nextjs:nodejs ecosystem.config.js ./

# Install PM2 globally
RUN npm install -g pm2

# Switch to non-root user
USER nextjs

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js

# Start the application with dumb-init and PM2
ENTRYPOINT ["dumb-init", "--"]
CMD ["pm2-runtime", "start", "ecosystem.config.js"]

PM2 Configuration

javascript
// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'element-plus-ssr',
      script: './server/index.js',
      instances: process.env.NODE_ENV === 'production' ? 'max' : 1,
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'development',
        PORT: 3000,
        HOST: '0.0.0.0'
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
        HOST: '0.0.0.0',
        MAX_MEMORY_RESTART: '1G',
        NODE_OPTIONS: '--max-old-space-size=1024'
      },
      // Monitoring and logging
      monitoring: true,
      pmx: true,
      
      // Auto restart configuration
      max_memory_restart: '1G',
      max_restarts: 10,
      min_uptime: '10s',
      
      // Graceful shutdown
      kill_timeout: 5000,
      wait_ready: true,
      listen_timeout: 3000,
      
      // Log configuration
      log_file: './logs/combined.log',
      out_file: './logs/out.log',
      error_file: './logs/error.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      
      // Advanced PM2 features
      merge_logs: true,
      autorestart: true,
      watch: false,
      
      // Environment-specific settings
      node_args: process.env.NODE_ENV === 'production' 
        ? ['--max-old-space-size=1024', '--optimize-for-size']
        : ['--inspect=0.0.0.0:9229'],
      
      // Health monitoring
      health_check_grace_period: 3000,
      health_check_fatal_exceptions: true
    }
  ],
  
  // Deployment configuration
  deploy: {
    production: {
      user: 'deploy',
      host: ['production-server-1', 'production-server-2'],
      ref: 'origin/main',
      repo: 'git@github.com:your-org/element-plus-ssr.git',
      path: '/var/www/element-plus-ssr',
      'post-deploy': 'pnpm install && pnpm run build && pnpm run build:ssr && pm2 reload ecosystem.config.js --env production',
      'pre-setup': 'apt update && apt install git -y'
    },
    staging: {
      user: 'deploy',
      host: 'staging-server',
      ref: 'origin/develop',
      repo: 'git@github.com:your-org/element-plus-ssr.git',
      path: '/var/www/element-plus-ssr-staging',
      'post-deploy': 'pnpm install && pnpm run build && pnpm run build:ssr && pm2 reload ecosystem.config.js --env staging'
    }
  }
}

Health Check Implementation

javascript
// healthcheck.js
const http = require('http')

const options = {
  hostname: 'localhost',
  port: process.env.PORT || 3000,
  path: '/health',
  method: 'GET',
  timeout: 3000
}

const request = http.request(options, (res) => {
  if (res.statusCode === 200) {
    process.exit(0)
  } else {
    console.error(`Health check failed with status: ${res.statusCode}`)
    process.exit(1)
  }
})

request.on('error', (err) => {
  console.error('Health check request failed:', err.message)
  process.exit(1)
})

request.on('timeout', () => {
  console.error('Health check request timed out')
  request.destroy()
  process.exit(1)
})

request.end()

Kubernetes Deployment

Kubernetes Manifests

yaml
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: element-plus-ssr
  labels:
    name: element-plus-ssr
    environment: production

---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: element-plus-ssr-config
  namespace: element-plus-ssr
data:
  NODE_ENV: "production"
  PORT: "3000"
  HOST: "0.0.0.0"
  LOG_LEVEL: "info"
  REDIS_HOST: "redis-service"
  REDIS_PORT: "6379"
  MONGODB_HOST: "mongodb-service"
  MONGODB_PORT: "27017"
  MONITORING_ENABLED: "true"
  CACHE_TTL: "300"

---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: element-plus-ssr-secrets
  namespace: element-plus-ssr
type: Opaque
data:
  # Base64 encoded values
  JWT_SECRET: <base64-encoded-jwt-secret>
  MONGODB_PASSWORD: <base64-encoded-mongodb-password>
  REDIS_PASSWORD: <base64-encoded-redis-password>
  API_KEY: <base64-encoded-api-key>

---
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: element-plus-ssr
  namespace: element-plus-ssr
  labels:
    app: element-plus-ssr
    version: v1
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: element-plus-ssr
  template:
    metadata:
      labels:
        app: element-plus-ssr
        version: v1
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "3000"
        prometheus.io/path: "/metrics"
    spec:
      serviceAccountName: element-plus-ssr
      securityContext:
        runAsNonRoot: true
        runAsUser: 1001
        fsGroup: 1001
      containers:
      - name: element-plus-ssr
        image: your-registry/element-plus-ssr:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 3000
          name: http
          protocol: TCP
        env:
        - name: NODE_ENV
          valueFrom:
            configMapKeyRef:
              name: element-plus-ssr-config
              key: NODE_ENV
        - name: PORT
          valueFrom:
            configMapKeyRef:
              name: element-plus-ssr-config
              key: PORT
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: element-plus-ssr-secrets
              key: JWT_SECRET
        - name: MONGODB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: element-plus-ssr-secrets
              key: MONGODB_PASSWORD
        envFrom:
        - configMapRef:
            name: element-plus-ssr-config
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        startupProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 30
        volumeMounts:
        - name: logs
          mountPath: /app/logs
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: logs
        emptyDir: {}
      - name: tmp
        emptyDir: {}
      nodeSelector:
        kubernetes.io/os: linux
      tolerations:
      - key: "node.kubernetes.io/not-ready"
        operator: "Exists"
        effect: "NoExecute"
        tolerationSeconds: 300
      - key: "node.kubernetes.io/unreachable"
        operator: "Exists"
        effect: "NoExecute"
        tolerationSeconds: 300

---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: element-plus-ssr-service
  namespace: element-plus-ssr
  labels:
    app: element-plus-ssr
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 3000
    protocol: TCP
    name: http
  selector:
    app: element-plus-ssr

---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: element-plus-ssr-ingress
  namespace: element-plus-ssr
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/rate-limit: "100"
    nginx.ingress.kubernetes.io/rate-limit-window: "1m"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - your-domain.com
    - www.your-domain.com
    secretName: element-plus-ssr-tls
  rules:
  - host: your-domain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: element-plus-ssr-service
            port:
              number: 80
  - host: www.your-domain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: element-plus-ssr-service
            port:
              number: 80

---
# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: element-plus-ssr-hpa
  namespace: element-plus-ssr
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: element-plus-ssr
  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
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
      - type: Pods
        value: 2
        periodSeconds: 60
      selectPolicy: Max

Service Account and RBAC

yaml
# k8s/rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: element-plus-ssr
  namespace: element-plus-ssr

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: element-plus-ssr
  name: element-plus-ssr-role
rules:
- apiGroups: [""]
  resources: ["pods", "services", "endpoints"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: element-plus-ssr-rolebinding
  namespace: element-plus-ssr
subjects:
- kind: ServiceAccount
  name: element-plus-ssr
  namespace: element-plus-ssr
roleRef:
  kind: Role
  name: element-plus-ssr-role
  apiGroup: rbac.authorization.k8s.io

CI/CD Pipeline

GitHub Actions Workflow

yaml
# .github/workflows/deploy.yml
name: Deploy Element Plus SSR

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

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'pnpm'
        
    - name: Install pnpm
      uses: pnpm/action-setup@v2
      with:
        version: 8
        
    - 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: Build application
      run: |
        pnpm run build
        pnpm run build:ssr
        
    - name: Upload build artifacts
      uses: actions/upload-artifact@v4
      with:
        name: build-artifacts
        path: |
          dist/
          dist-ssr/
        retention-days: 1

  security:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Run security audit
      run: |
        npm audit --audit-level=high
        
    - name: Run Snyk security scan
      uses: snyk/actions/node@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      with:
        args: --severity-threshold=high

  build-and-push:
    needs: [test, security]
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Download build artifacts
      uses: actions/download-artifact@v4
      with:
        name: build-artifacts
        
    - 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=sha,prefix={{branch}}-
          type=raw,value=latest,enable={{is_default_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 }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
        
    - name: Generate SBOM
      uses: anchore/sbom-action@v0
      with:
        image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
        format: spdx-json
        output-file: sbom.spdx.json
        
    - name: Upload SBOM
      uses: actions/upload-artifact@v4
      with:
        name: sbom
        path: sbom.spdx.json

  deploy-staging:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment: staging
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Setup kubectl
      uses: azure/setup-kubectl@v3
      with:
        version: 'v1.28.0'
        
    - name: Configure kubectl
      run: |
        echo "${{ secrets.KUBE_CONFIG_STAGING }}" | base64 -d > kubeconfig
        export KUBECONFIG=kubeconfig
        
    - name: Deploy to staging
      run: |
        export KUBECONFIG=kubeconfig
        
        # Update image in deployment
        kubectl set image deployment/element-plus-ssr \
          element-plus-ssr=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop \
          -n element-plus-ssr-staging
          
        # Wait for rollout to complete
        kubectl rollout status deployment/element-plus-ssr \
          -n element-plus-ssr-staging --timeout=300s
          
        # Verify deployment
        kubectl get pods -n element-plus-ssr-staging
        
    - name: Run smoke tests
      run: |
        # Wait for service to be ready
        sleep 30
        
        # Run basic health checks
        curl -f https://staging.your-domain.com/health || exit 1
        curl -f https://staging.your-domain.com/ready || exit 1

  deploy-production:
    needs: [build-and-push, deploy-staging]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Setup kubectl
      uses: azure/setup-kubectl@v3
      with:
        version: 'v1.28.0'
        
    - name: Configure kubectl
      run: |
        echo "${{ secrets.KUBE_CONFIG_PRODUCTION }}" | base64 -d > kubeconfig
        export KUBECONFIG=kubeconfig
        
    - name: Deploy to production
      run: |
        export KUBECONFIG=kubeconfig
        
        # Update image in deployment
        kubectl set image deployment/element-plus-ssr \
          element-plus-ssr=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
          -n element-plus-ssr
          
        # Wait for rollout to complete
        kubectl rollout status deployment/element-plus-ssr \
          -n element-plus-ssr --timeout=600s
          
        # Verify deployment
        kubectl get pods -n element-plus-ssr
        
    - name: Run production smoke tests
      run: |
        # Wait for service to be ready
        sleep 60
        
        # Run comprehensive health checks
        curl -f https://your-domain.com/health || exit 1
        curl -f https://your-domain.com/ready || exit 1
        
        # Test critical user journeys
        npm run test:smoke-production
        
    - name: Notify deployment success
      uses: 8398a7/action-slack@v3
      with:
        status: success
        text: '🚀 Production deployment successful!'
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      if: success()
      
    - name: Notify deployment failure
      uses: 8398a7/action-slack@v3
      with:
        status: failure
        text: '❌ Production deployment failed!'
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      if: failure()

Monitoring and Observability

Prometheus Metrics

typescript
// src/server/monitoring/metrics.ts
import { register, Counter, Histogram, Gauge } from 'prom-client'
import { Request, Response, NextFunction } from 'express'

// Create metrics
const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code']
})

const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10]
})

const ssrRenderDuration = new Histogram({
  name: 'ssr_render_duration_seconds',
  help: 'Duration of SSR rendering in seconds',
  labelNames: ['route'],
  buckets: [0.1, 0.2, 0.5, 1, 2, 5]
})

const activeConnections = new Gauge({
  name: 'active_connections',
  help: 'Number of active connections'
})

const memoryUsage = new Gauge({
  name: 'nodejs_memory_usage_bytes',
  help: 'Node.js memory usage in bytes',
  labelNames: ['type']
})

const cacheHitRate = new Counter({
  name: 'cache_hits_total',
  help: 'Total number of cache hits',
  labelNames: ['cache_type']
})

const cacheMissRate = new Counter({
  name: 'cache_misses_total',
  help: 'Total number of cache misses',
  labelNames: ['cache_type']
})

// Register metrics
register.registerMetric(httpRequestsTotal)
register.registerMetric(httpRequestDuration)
register.registerMetric(ssrRenderDuration)
register.registerMetric(activeConnections)
register.registerMetric(memoryUsage)
register.registerMetric(cacheHitRate)
register.registerMetric(cacheMissRate)

// Middleware for HTTP metrics
export const metricsMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const start = Date.now()
  
  // Increment active connections
  activeConnections.inc()
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000
    const route = req.route?.path || req.path
    
    // Record metrics
    httpRequestsTotal.inc({
      method: req.method,
      route,
      status_code: res.statusCode
    })
    
    httpRequestDuration.observe(
      {
        method: req.method,
        route,
        status_code: res.statusCode
      },
      duration
    )
    
    // Decrement active connections
    activeConnections.dec()
  })
  
  next()
}

// Memory usage collector
setInterval(() => {
  const usage = process.memoryUsage()
  
  memoryUsage.set({ type: 'heap_used' }, usage.heapUsed)
  memoryUsage.set({ type: 'heap_total' }, usage.heapTotal)
  memoryUsage.set({ type: 'external' }, usage.external)
  memoryUsage.set({ type: 'rss' }, usage.rss)
}, 10000)

// SSR render time tracking
export const trackSSRRender = (route: string, duration: number) => {
  ssrRenderDuration.observe({ route }, duration / 1000)
}

// Cache metrics tracking
export const trackCacheHit = (cacheType: string) => {
  cacheHitRate.inc({ cache_type: cacheType })
}

export const trackCacheMiss = (cacheType: string) => {
  cacheMissRate.inc({ cache_type: cacheType })
}

// Metrics endpoint
export const metricsHandler = async (req: Request, res: Response) => {
  try {
    res.set('Content-Type', register.contentType)
    res.end(await register.metrics())
  } catch (error) {
    res.status(500).end(error)
  }
}

Logging Configuration

typescript
// src/server/logging/logger.ts
import winston from 'winston'
import 'winston-daily-rotate-file'

const { combine, timestamp, errors, json, printf, colorize } = winston.format

// Custom format for console output
const consoleFormat = printf(({ level, message, timestamp, ...meta }) => {
  return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`
})

// Create logger instance
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: combine(
    timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    errors({ stack: true }),
    json()
  ),
  defaultMeta: {
    service: 'element-plus-ssr',
    version: process.env.npm_package_version,
    environment: process.env.NODE_ENV
  },
  transports: [
    // Console transport for development
    new winston.transports.Console({
      format: combine(
        colorize(),
        timestamp({ format: 'HH:mm:ss' }),
        consoleFormat
      ),
      silent: process.env.NODE_ENV === 'test'
    }),
    
    // File transport for all logs
    new winston.transports.DailyRotateFile({
      filename: 'logs/application-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
      format: combine(
        timestamp(),
        json()
      )
    }),
    
    // Separate file for errors
    new winston.transports.DailyRotateFile({
      filename: 'logs/error-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      level: 'error',
      maxSize: '20m',
      maxFiles: '30d',
      format: combine(
        timestamp(),
        json()
      )
    })
  ],
  
  // Handle uncaught exceptions and rejections
  exceptionHandlers: [
    new winston.transports.DailyRotateFile({
      filename: 'logs/exceptions-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '30d'
    })
  ],
  
  rejectionHandlers: [
    new winston.transports.DailyRotateFile({
      filename: 'logs/rejections-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '30d'
    })
  ]
})

// Add structured logging methods
export const structuredLogger = {
  info: (message: string, meta?: any) => {
    logger.info(message, meta)
  },
  
  error: (message: string, error?: Error, meta?: any) => {
    logger.error(message, {
      error: error ? {
        message: error.message,
        stack: error.stack,
        name: error.name
      } : undefined,
      ...meta
    })
  },
  
  warn: (message: string, meta?: any) => {
    logger.warn(message, meta)
  },
  
  debug: (message: string, meta?: any) => {
    logger.debug(message, meta)
  },
  
  // Request logging
  request: (req: any, res: any, duration: number) => {
    logger.info('HTTP Request', {
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      userAgent: req.get('User-Agent'),
      ip: req.ip,
      requestId: req.requestId
    })
  },
  
  // SSR logging
  ssr: (route: string, duration: number, cacheHit: boolean) => {
    logger.info('SSR Render', {
      route,
      duration: `${duration}ms`,
      cacheHit,
      type: 'ssr_render'
    })
  },
  
  // Performance logging
  performance: (metric: string, value: number, unit: string, meta?: any) => {
    logger.info('Performance Metric', {
      metric,
      value,
      unit,
      type: 'performance',
      ...meta
    })
  }
}

export default logger

This comprehensive guide covers deployment and operations for Element Plus SSR applications, including containerization, Kubernetes orchestration, CI/CD pipelines, and monitoring strategies.

Element Plus Study Guide