Performance Optimization - RumenDamyanov/js-chess GitHub Wiki

Performance Optimization

Comprehensive guide to optimizing performance across all framework implementations in the chess showcase.

Overview

Performance optimization in this chess showcase covers:

  • Frontend bundle optimization
  • Runtime performance tuning
  • Memory management
  • Network efficiency
  • Backend integration optimization
  • Real-time communication performance
  • Mobile performance considerations

Bundle Optimization

Code Splitting and Lazy Loading

Angular Implementation

// angular-chess/src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    redirectTo: '/game',
    pathMatch: 'full'
  },
  {
    path: 'game',
    loadComponent: () => import('./components/game/game.component').then(m => m.GameComponent)
  },
  {
    path: 'analysis',
    loadComponent: () => import('./components/analysis/analysis.component').then(m => m.AnalysisComponent)
  },
  {
    path: 'settings',
    loadComponent: () => import('./components/settings/settings.component').then(m => m.SettingsComponent)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, {
    preloadingStrategy: PreloadAllModules, // Preload after initial load
    enableTracing: false // Set to true for debugging
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }
// angular-chess/src/app/components/game/game.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';

// Lazy load heavy components
@Component({
  selector: 'app-game',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="game-container">
      <app-chess-board></app-chess-board>

      <!-- Lazy load analysis panel -->
      @if (showAnalysis) {
        <app-analysis-panel></app-analysis-panel>
      }

      <!-- Lazy load chat component -->
      @if (showChat) {
        <app-ai-chat></app-ai-chat>
      }
    </div>
  `
})
export class GameComponent implements OnInit {
  showAnalysis = false;
  showChat = false;

  async loadAnalysis() {
    if (!this.showAnalysis) {
      // Dynamic import for heavy analysis features
      const { AnalysisPanelComponent } = await import('../analysis/analysis-panel.component');
      this.showAnalysis = true;
    }
  }

  async loadChat() {
    if (!this.showChat) {
      // Dynamic import for AI chat
      const { AiChatComponent } = await import('../ai-chat/ai-chat.component');
      this.showChat = true;
    }
  }
}

React Implementation

// react-chess/src/App.jsx
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import LoadingSpinner from './components/LoadingSpinner';

// Lazy load route components
const Game = lazy(() => import('./pages/Game'));
const Analysis = lazy(() => import('./pages/Analysis'));
const Settings = lazy(() => import('./pages/Settings'));

// Lazy load heavy features
const AIChat = lazy(() => import('./components/AIChat'));
const AnalysisPanel = lazy(() => import('./components/AnalysisPanel'));

function App() {
  return (
    <Router>
      <div className="app">
        <Suspense fallback={<LoadingSpinner />}>
          <Routes>
            <Route path="/" element={<Game />} />
            <Route path="/analysis" element={<Analysis />} />
            <Route path="/settings" element={<Settings />} />
          </Routes>
        </Suspense>
      </div>
    </Router>
  );
}

export default App;
// react-chess/src/hooks/useLazyComponent.js
import { useState, useCallback } from 'react';

export function useLazyComponent(importFn) {
  const [Component, setComponent] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const loadComponent = useCallback(async () => {
    if (Component) return Component;

    setLoading(true);
    setError(null);

    try {
      const module = await importFn();
      const LoadedComponent = module.default || module;
      setComponent(() => LoadedComponent);
      return LoadedComponent;
    } catch (err) {
      setError(err);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [importFn, Component]);

  return { Component, loading, error, loadComponent };
}

Vue Implementation

// vue-chess/src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    name: 'Game',
    component: () => import('../views/GameView.vue')
  },
  {
    path: '/analysis',
    name: 'Analysis',
    component: () => import('../views/AnalysisView.vue')
  },
  {
    path: '/settings',
    name: 'Settings',
    component: () => import('../views/SettingsView.vue')
  }
];

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

export default router;
<!-- vue-chess/src/components/GameBoard.vue -->
<template>
  <div class="game-board">
    <ChessBoard />

    <!-- Lazy load heavy components -->
    <Suspense>
      <AnalysisPanel v-if="showAnalysis" />
      <template #fallback>
        <div class="loading">Loading analysis...</div>
      </template>
    </Suspense>

    <Suspense>
      <AIChat v-if="showChat" />
      <template #fallback>
        <div class="loading">Loading chat...</div>
      </template>
    </Suspense>
  </div>
</template>

<script setup>
import { ref, defineAsyncComponent } from 'vue';
import ChessBoard from './ChessBoard.vue';

// Lazy load heavy components
const AnalysisPanel = defineAsyncComponent(() => import('./AnalysisPanel.vue'));
const AIChat = defineAsyncComponent(() => import('./AIChat.vue'));

const showAnalysis = ref(false);
const showChat = ref(false);
</script>

Webpack Optimization

// webpack.config.js (for React and Vue)
const path = require('path');
const webpack = require('webpack');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
  // Production optimizations
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 20
        },
        chess: {
          test: /[\\/]src[\\/]chess[\\/]/,
          name: 'chess-engine',
          chunks: 'all',
          priority: 10
        },
        ai: {
          test: /[\\/]src[\\/]ai[\\/]/,
          name: 'ai-features',
          chunks: 'all',
          priority: 10
        }
      }
    },
    usedExports: true,
    sideEffects: false,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true
          }
        }
      })
    ]
  },

  // Tree shaking
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'chess-engine': path.resolve(__dirname, 'src/chess'),
      'shared': path.resolve(__dirname, '../shared')
    }
  },

  // Performance budgets
  performance: {
    maxAssetSize: 250000,
    maxEntrypointSize: 250000,
    hints: 'warning'
  },

  plugins: [
    // Analyze bundle size
    process.env.ANALYZE && new BundleAnalyzerPlugin(),

    // Define environment variables
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    })
  ].filter(Boolean)
};

Tree Shaking Optimization

// shared/utils/index.js - Proper ES module exports
export { ChessEngine } from './chess-engine';
export { AIAnalyzer } from './ai-analyzer';
export { WebSocketClient } from './websocket-client';
export { GameHistory } from './game-history';

// Individual exports for better tree shaking
export const moveValidation = {
  isValidMove,
  isPieceBlocked,
  isInCheck
};

export const boardUtils = {
  createBoard,
  cloneBoard,
  getBoardFEN
};
// Optimized imports in components
// ❌ Bad - imports entire library
import * as utils from 'shared/utils';

// ✅ Good - imports only what's needed
import { ChessEngine, moveValidation } from 'shared/utils';
import { isValidMove } from 'shared/utils/move-validation';

Runtime Performance

Memory Management

Object Pooling for Game State

// shared/performance/ObjectPool.js
export class ObjectPool {
  constructor(createFn, resetFn, initialSize = 10) {
    this.createFn = createFn;
    this.resetFn = resetFn;
    this.pool = [];
    this.active = new Set();

    // Pre-populate pool
    for (let i = 0; i < initialSize; i++) {
      this.pool.push(this.createFn());
    }
  }

  acquire() {
    let obj;

    if (this.pool.length > 0) {
      obj = this.pool.pop();
    } else {
      obj = this.createFn();
    }

    this.active.add(obj);
    return obj;
  }

  release(obj) {
    if (this.active.has(obj)) {
      this.active.delete(obj);
      this.resetFn(obj);
      this.pool.push(obj);
    }
  }

  clear() {
    this.pool.length = 0;
    this.active.clear();
  }

  getStats() {
    return {
      poolSize: this.pool.length,
      activeCount: this.active.size,
      totalAllocated: this.pool.length + this.active.size
    };
  }
}

// Usage for chess positions
export const positionPool = new ObjectPool(
  () => ({
    board: new Array(64).fill(null),
    turn: 'white',
    castling: { K: true, Q: true, k: true, q: true },
    enPassant: null,
    halfmove: 0,
    fullmove: 1
  }),
  (position) => {
    position.board.fill(null);
    position.turn = 'white';
    position.castling = { K: true, Q: true, k: true, q: true };
    position.enPassant = null;
    position.halfmove = 0;
    position.fullmove = 1;
  },
  50 // Pre-allocate 50 positions
);

Efficient Move Generation

// shared/chess/MoveGenerator.js
export class OptimizedMoveGenerator {
  constructor() {
    // Pre-computed move patterns
    this.knightMoves = this.precomputeKnightMoves();
    this.kingMoves = this.precomputeKingMoves();
    this.rayDirections = [
      [-1, -1], [-1, 0], [-1, 1],
      [0, -1],           [0, 1],
      [1, -1],  [1, 0],  [1, 1]
    ];

    // Bitboard representations for faster computation
    this.bitboards = {
      white: 0n,
      black: 0n,
      occupied: 0n
    };
  }

  precomputeKnightMoves() {
    const moves = new Array(64);
    const deltas = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]];

    for (let square = 0; square < 64; square++) {
      const rank = Math.floor(square / 8);
      const file = square % 8;
      moves[square] = [];

      for (const [dr, df] of deltas) {
        const newRank = rank + dr;
        const newFile = file + df;

        if (newRank >= 0 && newRank < 8 && newFile >= 0 && newFile < 8) {
          moves[square].push(newRank * 8 + newFile);
        }
      }
    }

    return moves;
  }

  precomputeKingMoves() {
    const moves = new Array(64);

    for (let square = 0; square < 64; square++) {
      const rank = Math.floor(square / 8);
      const file = square % 8;
      moves[square] = [];

      for (let dr = -1; dr <= 1; dr++) {
        for (let df = -1; df <= 1; df++) {
          if (dr === 0 && df === 0) continue;

          const newRank = rank + dr;
          const newFile = file + df;

          if (newRank >= 0 && newRank < 8 && newFile >= 0 && newFile < 8) {
            moves[square].push(newRank * 8 + newFile);
          }
        }
      }
    }

    return moves;
  }

  generateMoves(position, piece, square) {
    const moves = [];
    const pieceType = piece.type.toLowerCase();

    switch (pieceType) {
      case 'pawn':
        return this.generatePawnMoves(position, piece, square);
      case 'knight':
        return this.generateKnightMoves(position, piece, square);
      case 'bishop':
        return this.generateBishopMoves(position, piece, square);
      case 'rook':
        return this.generateRookMoves(position, piece, square);
      case 'queen':
        return this.generateQueenMoves(position, piece, square);
      case 'king':
        return this.generateKingMoves(position, piece, square);
      default:
        return moves;
    }
  }

  generateKnightMoves(position, piece, square) {
    const moves = [];
    const precomputed = this.knightMoves[square];

    for (const targetSquare of precomputed) {
      const targetPiece = position.board[targetSquare];

      if (!targetPiece || targetPiece.color !== piece.color) {
        moves.push({
          from: square,
          to: targetSquare,
          piece: piece,
          capture: targetPiece
        });
      }
    }

    return moves;
  }

  // Use typed arrays for better performance
  generateSlidingMoves(position, piece, square, directions) {
    const moves = [];
    const rank = Math.floor(square / 8);
    const file = square % 8;

    for (const [dr, df] of directions) {
      let currentRank = rank + dr;
      let currentFile = file + df;

      while (currentRank >= 0 && currentRank < 8 && currentFile >= 0 && currentFile < 8) {
        const targetSquare = currentRank * 8 + currentFile;
        const targetPiece = position.board[targetSquare];

        if (targetPiece) {
          if (targetPiece.color !== piece.color) {
            moves.push({
              from: square,
              to: targetSquare,
              piece: piece,
              capture: targetPiece
            });
          }
          break; // Can't continue in this direction
        } else {
          moves.push({
            from: square,
            to: targetSquare,
            piece: piece,
            capture: null
          });
        }

        currentRank += dr;
        currentFile += df;
      }
    }

    return moves;
  }
}

Virtual Scrolling for Move History

// shared/components/VirtualMoveList.js
export class VirtualMoveList {
  constructor(container, itemHeight = 40, bufferSize = 5) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.bufferSize = bufferSize;
    this.scrollTop = 0;
    this.items = [];
    this.renderedItems = new Map();

    this.setupScrollListener();
  }

  setItems(items) {
    this.items = items;
    this.updateVirtualList();
  }

  setupScrollListener() {
    let ticking = false;

    this.container.addEventListener('scroll', () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          this.handleScroll();
          ticking = false;
        });
        ticking = true;
      }
    });
  }

  handleScroll() {
    this.scrollTop = this.container.scrollTop;
    this.updateVirtualList();
  }

  updateVirtualList() {
    const containerHeight = this.container.clientHeight;
    const totalHeight = this.items.length * this.itemHeight;

    // Calculate visible range
    const startIndex = Math.max(0, Math.floor(this.scrollTop / this.itemHeight) - this.bufferSize);
    const endIndex = Math.min(
      this.items.length - 1,
      Math.ceil((this.scrollTop + containerHeight) / this.itemHeight) + this.bufferSize
    );

    // Update container height
    this.container.style.height = `${totalHeight}px`;

    // Remove items outside visible range
    for (const [index, element] of this.renderedItems) {
      if (index < startIndex || index > endIndex) {
        element.remove();
        this.renderedItems.delete(index);
      }
    }

    // Add items in visible range
    for (let i = startIndex; i <= endIndex; i++) {
      if (!this.renderedItems.has(i) && this.items[i]) {
        const element = this.createItemElement(this.items[i], i);
        this.container.appendChild(element);
        this.renderedItems.set(i, element);
      }
    }
  }

  createItemElement(item, index) {
    const element = document.createElement('div');
    element.className = 'move-item';
    element.style.cssText = `
      position: absolute;
      top: ${index * this.itemHeight}px;
      height: ${this.itemHeight}px;
      width: 100%;
    `;
    element.innerHTML = this.renderItem(item, index);
    return element;
  }

  renderItem(move, index) {
    const moveNumber = Math.floor(index / 2) + 1;
    const isWhite = index % 2 === 0;

    return `
      <div class="move-content">
        ${isWhite ? `<span class="move-number">${moveNumber}.</span>` : ''}
        <span class="move-notation">${move.notation}</span>
        <span class="move-time">${move.time || ''}</span>
      </div>
    `;
  }
}

Network Optimization

API Request Optimization

// shared/api/OptimizedApiClient.js
export class OptimizedApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.cache = new Map();
    this.requestQueue = [];
    this.batchTimer = null;
    this.retryCount = new Map();
    this.maxRetries = 3;
  }

  // Request batching
  batchRequest(requests) {
    this.requestQueue.push(...requests);

    if (!this.batchTimer) {
      this.batchTimer = setTimeout(() => {
        this.processBatch();
      }, 10); // 10ms batching window
    }
  }

  async processBatch() {
    if (this.requestQueue.length === 0) return;

    const batch = [...this.requestQueue];
    this.requestQueue.length = 0;
    this.batchTimer = null;

    // Group by endpoint
    const grouped = batch.reduce((acc, req) => {
      const key = `${req.method}:${req.endpoint}`;
      if (!acc[key]) acc[key] = [];
      acc[key].push(req);
      return acc;
    }, {});

    // Process each group
    await Promise.all(
      Object.values(grouped).map(group => this.processRequestGroup(group))
    );
  }

  async processRequestGroup(requests) {
    if (requests.length === 1) {
      return this.processRequest(requests[0]);
    }

    // Batch multiple requests of the same type
    const batchEndpoint = requests[0].endpoint + '/batch';
    const batchData = requests.map(req => req.data);

    try {
      const response = await fetch(this.baseURL + batchEndpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(batchData)
      });

      const results = await response.json();

      // Resolve individual promises
      requests.forEach((req, index) => {
        req.resolve(results[index]);
      });
    } catch (error) {
      // Fallback to individual requests
      await Promise.all(requests.map(req => this.processRequest(req)));
    }
  }

  // Request deduplication
  async request(method, endpoint, data = null, options = {}) {
    const cacheKey = `${method}:${endpoint}:${JSON.stringify(data)}`;

    // Check cache for GET requests
    if (method === 'GET' && this.cache.has(cacheKey)) {
      const cached = this.cache.get(cacheKey);
      if (Date.now() - cached.timestamp < (options.cacheTime || 5000)) {
        return cached.data;
      }
    }

    return new Promise((resolve, reject) => {
      const request = {
        method,
        endpoint,
        data,
        options,
        resolve,
        reject,
        cacheKey
      };

      // Add to batch or process immediately
      if (options.batch !== false) {
        this.batchRequest([request]);
      } else {
        this.processRequest(request);
      }
    });
  }

  async processRequest(request) {
    const { method, endpoint, data, options, resolve, reject, cacheKey } = request;

    try {
      const response = await this.fetchWithRetry(method, endpoint, data, options);
      const result = await response.json();

      // Cache GET requests
      if (method === 'GET') {
        this.cache.set(cacheKey, {
          data: result,
          timestamp: Date.now()
        });
      }

      resolve(result);
    } catch (error) {
      reject(error);
    }
  }

  async fetchWithRetry(method, endpoint, data, options) {
    const url = this.baseURL + endpoint;
    const retryKey = `${method}:${endpoint}`;
    let attempt = this.retryCount.get(retryKey) || 0;

    while (attempt < this.maxRetries) {
      try {
        const response = await fetch(url, {
          method,
          headers: {
            'Content-Type': 'application/json',
            ...options.headers
          },
          body: data ? JSON.stringify(data) : undefined,
          signal: options.signal
        });

        if (response.ok) {
          this.retryCount.delete(retryKey);
          return response;
        }

        if (response.status >= 400 && response.status < 500) {
          // Client errors shouldn't be retried
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      } catch (error) {
        attempt++;
        this.retryCount.set(retryKey, attempt);

        if (attempt >= this.maxRetries) {
          this.retryCount.delete(retryKey);
          throw error;
        }

        // Exponential backoff
        const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  clearCache() {
    this.cache.clear();
  }

  getCacheStats() {
    return {
      size: this.cache.size,
      entries: Array.from(this.cache.keys())
    };
  }
}

// Global API client instance
export const apiClient = new OptimizedApiClient('/api');

WebSocket Connection Optimization

// shared/websocket/OptimizedWebSocket.js
export class OptimizedWebSocket {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      reconnectInterval: 1000,
      maxReconnectInterval: 30000,
      reconnectDecay: 1.5,
      maxReconnectAttempts: 10,
      binaryType: 'arraybuffer',
      ...options
    };

    this.ws = null;
    this.reconnectAttempts = 0;
    this.messageQueue = [];
    this.subscriptions = new Map();
    this.isConnected = false;

    // Message compression
    this.compressionEnabled = options.compression !== false;
    this.messageBuffer = [];
    this.flushTimer = null;

    this.connect();
  }

  connect() {
    try {
      this.ws = new WebSocket(this.url);
      this.ws.binaryType = this.options.binaryType;

      this.ws.onopen = this.handleOpen.bind(this);
      this.ws.onmessage = this.handleMessage.bind(this);
      this.ws.onclose = this.handleClose.bind(this);
      this.ws.onerror = this.handleError.bind(this);
    } catch (error) {
      this.handleError(error);
    }
  }

  handleOpen() {
    this.isConnected = true;
    this.reconnectAttempts = 0;

    // Send queued messages
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      this.send(message.data, message.options);
    }

    this.emit('connect');
  }

  handleMessage(event) {
    try {
      let data = event.data;

      // Handle binary messages
      if (data instanceof ArrayBuffer) {
        data = this.decompressMessage(data);
      } else {
        data = JSON.parse(data);
      }

      // Route to appropriate handler
      if (data.type && this.subscriptions.has(data.type)) {
        const handlers = this.subscriptions.get(data.type);
        handlers.forEach(handler => handler(data));
      }

      this.emit('message', data);
    } catch (error) {
      console.error('Error parsing WebSocket message:', error);
    }
  }

  handleClose(event) {
    this.isConnected = false;
    this.emit('disconnect', event);

    if (!event.wasClean && this.reconnectAttempts < this.options.maxReconnectAttempts) {
      this.scheduleReconnect();
    }
  }

  handleError(error) {
    this.emit('error', error);
  }

  scheduleReconnect() {
    const interval = Math.min(
      this.options.reconnectInterval * Math.pow(this.options.reconnectDecay, this.reconnectAttempts),
      this.options.maxReconnectInterval
    );

    setTimeout(() => {
      this.reconnectAttempts++;
      this.connect();
    }, interval);
  }

  send(data, options = {}) {
    if (!this.isConnected) {
      this.messageQueue.push({ data, options });
      return;
    }

    try {
      let message = data;

      // Compress large messages
      if (this.compressionEnabled && JSON.stringify(data).length > 1024) {
        message = this.compressMessage(data);
      } else {
        message = JSON.stringify(data);
      }

      this.ws.send(message);
    } catch (error) {
      console.error('Error sending WebSocket message:', error);
    }
  }

  // Message batching for high-frequency updates
  batchSend(data, options = {}) {
    this.messageBuffer.push(data);

    if (!this.flushTimer) {
      this.flushTimer = setTimeout(() => {
        this.flushMessages();
      }, options.batchDelay || 16); // ~60fps
    }
  }

  flushMessages() {
    if (this.messageBuffer.length === 0) return;

    const batch = {
      type: 'batch',
      messages: [...this.messageBuffer]
    };

    this.messageBuffer.length = 0;
    this.flushTimer = null;

    this.send(batch);
  }

  subscribe(messageType, handler) {
    if (!this.subscriptions.has(messageType)) {
      this.subscriptions.set(messageType, new Set());
    }

    this.subscriptions.get(messageType).add(handler);

    return () => {
      const handlers = this.subscriptions.get(messageType);
      if (handlers) {
        handlers.delete(handler);
        if (handlers.size === 0) {
          this.subscriptions.delete(messageType);
        }
      }
    };
  }

  compressMessage(data) {
    // Simple compression - in practice, use libraries like pako
    const json = JSON.stringify(data);

    // Placeholder for actual compression
    return new TextEncoder().encode(json);
  }

  decompressMessage(buffer) {
    // Placeholder for actual decompression
    const json = new TextDecoder().decode(buffer);
    return JSON.parse(json);
  }

  emit(event, data) {
    const handlers = this.subscriptions.get(event);
    if (handlers) {
      handlers.forEach(handler => handler(data));
    }
  }

  close() {
    if (this.ws) {
      this.ws.close();
    }
  }
}

Mobile Performance

Touch Optimization

// shared/mobile/TouchOptimizer.js
export class TouchOptimizer {
  constructor(element) {
    this.element = element;
    this.isTouch = false;
    this.lastTouchTime = 0;
    this.touchStartPos = { x: 0, y: 0 };

    this.setupTouchHandlers();
  }

  setupTouchHandlers() {
    // Passive listeners for better performance
    this.element.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true });
    this.element.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false });
    this.element.addEventListener('touchend', this.handleTouchEnd.bind(this), { passive: true });

    // Prevent 300ms click delay
    this.element.addEventListener('click', this.handleClick.bind(this));
  }

  handleTouchStart(event) {
    this.isTouch = true;
    this.lastTouchTime = Date.now();

    const touch = event.touches[0];
    this.touchStartPos = { x: touch.clientX, y: touch.clientY };
  }

  handleTouchMove(event) {
    const touch = event.touches[0];
    const deltaX = Math.abs(touch.clientX - this.touchStartPos.x);
    const deltaY = Math.abs(touch.clientY - this.touchStartPos.y);

    // Prevent scrolling if user is dragging a piece
    if (deltaX > 10 || deltaY > 10) {
      event.preventDefault();
    }
  }

  handleTouchEnd(event) {
    // Reset touch state after a delay
    setTimeout(() => {
      this.isTouch = false;
    }, 300);
  }

  handleClick(event) {
    // Ignore clicks that come shortly after touch events
    if (this.isTouch && Date.now() - this.lastTouchTime < 300) {
      event.preventDefault();
      event.stopPropagation();
    }
  }
}

Viewport and Layout Optimization

/* Mobile-specific optimizations */
.chess-board {
  /* Hardware acceleration */
  transform: translateZ(0);
  will-change: transform;

  /* Prevent selection on mobile */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;

  /* Smooth scrolling */
  -webkit-overflow-scrolling: touch;
}

.chess-piece {
  /* Optimize animations */
  transform: translateZ(0);
  will-change: transform, opacity;

  /* Prevent flickering during drag */
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* Touch-friendly sizing */
@media (max-width: 768px) {
  .chess-square {
    min-height: 44px; /* iOS minimum touch target */
    min-width: 44px;
  }

  .game-controls button {
    min-height: 44px;
    padding: 12px 16px;
  }
}

/* Reduce motion for better performance */
@media (prefers-reduced-motion: reduce) {
  .chess-piece {
    animation: none !important;
    transition: none !important;
  }
}

Performance Monitoring

Performance Metrics Collection

// shared/performance/PerformanceMonitor.js
export class PerformanceMonitor {
  constructor() {
    this.metrics = new Map();
    this.observers = [];
    this.isEnabled = true;

    this.setupObservers();
  }

  setupObservers() {
    // Performance Observer for various metrics
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          this.recordMetric(entry.name, entry.duration, entry.entryType);
        }
      });

      observer.observe({ entryTypes: ['measure', 'navigation', 'paint'] });
      this.observers.push(observer);
    }

    // Long task observer
    if ('PerformanceObserver' in window) {
      const longTaskObserver = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          this.recordLongTask(entry);
        }
      });

      longTaskObserver.observe({ entryTypes: ['longtask'] });
      this.observers.push(longTaskObserver);
    }

    // Memory usage monitoring
    this.startMemoryMonitoring();
  }

  recordMetric(name, value, type) {
    if (!this.isEnabled) return;

    const key = `${type}:${name}`;

    if (!this.metrics.has(key)) {
      this.metrics.set(key, {
        count: 0,
        total: 0,
        min: Infinity,
        max: -Infinity,
        values: []
      });
    }

    const metric = this.metrics.get(key);
    metric.count++;
    metric.total += value;
    metric.min = Math.min(metric.min, value);
    metric.max = Math.max(metric.max, value);
    metric.values.push({ value, timestamp: Date.now() });

    // Keep only last 100 values
    if (metric.values.length > 100) {
      metric.values = metric.values.slice(-100);
    }
  }

  recordLongTask(entry) {
    console.warn('Long task detected:', {
      duration: entry.duration,
      startTime: entry.startTime,
      name: entry.name
    });

    this.recordMetric('long-task', entry.duration, 'longtask');
  }

  startMemoryMonitoring() {
    if (!('memory' in performance)) return;

    setInterval(() => {
      const memory = performance.memory;

      this.recordMetric('memory-used', memory.usedJSHeapSize, 'memory');
      this.recordMetric('memory-total', memory.totalJSHeapSize, 'memory');
      this.recordMetric('memory-limit', memory.jsHeapSizeLimit, 'memory');
    }, 5000); // Check every 5 seconds
  }

  measureFunction(name, fn) {
    return (...args) => {
      const start = performance.now();

      try {
        const result = fn.apply(this, args);

        if (result && typeof result.then === 'function') {
          // Handle async functions
          return result.finally(() => {
            const duration = performance.now() - start;
            this.recordMetric(name, duration, 'function');
          });
        } else {
          const duration = performance.now() - start;
          this.recordMetric(name, duration, 'function');
          return result;
        }
      } catch (error) {
        const duration = performance.now() - start;
        this.recordMetric(name, duration, 'function');
        throw error;
      }
    };
  }

  getMetrics() {
    const result = {};

    for (const [key, metric] of this.metrics) {
      result[key] = {
        count: metric.count,
        average: metric.total / metric.count,
        min: metric.min,
        max: metric.max,
        total: metric.total
      };
    }

    return result;
  }

  getWebVitals() {
    return new Promise((resolve) => {
      const vitals = {};

      // Largest Contentful Paint
      new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lastEntry = entries[entries.length - 1];
        vitals.lcp = lastEntry.renderTime || lastEntry.loadTime;
        checkComplete();
      }).observe({ entryTypes: ['largest-contentful-paint'] });

      // First Input Delay
      new PerformanceObserver((list) => {
        const firstEntry = list.getEntries()[0];
        vitals.fid = firstEntry.processingStart - firstEntry.startTime;
        checkComplete();
      }).observe({ entryTypes: ['first-input'] });

      // Cumulative Layout Shift
      let clsValue = 0;
      new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (!entry.hadRecentInput) {
            clsValue += entry.value;
          }
        }
        vitals.cls = clsValue;
        checkComplete();
      }).observe({ entryTypes: ['layout-shift'] });

      function checkComplete() {
        if (vitals.lcp && vitals.fid && vitals.cls !== undefined) {
          resolve(vitals);
        }
      }

      // Timeout after 10 seconds
      setTimeout(() => resolve(vitals), 10000);
    });
  }

  exportMetrics() {
    return {
      timestamp: Date.now(),
      url: window.location.href,
      userAgent: navigator.userAgent,
      metrics: this.getMetrics(),
      webVitals: this.getWebVitals()
    };
  }

  clear() {
    this.metrics.clear();
  }

  disable() {
    this.isEnabled = false;
    this.observers.forEach(observer => observer.disconnect());
    this.observers = [];
  }
}

// Global performance monitor
export const performanceMonitor = new PerformanceMonitor();

// Convenience decorators
export function measurePerformance(name) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = performanceMonitor.measureFunction(`${target.constructor.name}.${propertyKey}`, originalMethod);
    return descriptor;
  };
}

Build and Deployment Optimization

Docker Multi-stage Builds

# Optimized Dockerfile for production
FROM node:18-alpine AS base
WORKDIR /app
RUN apk add --no-cache libc6-compat

# Dependencies stage
FROM base AS deps
COPY package*.json ./
RUN npm ci --only=production --ignore-scripts && npm cache clean --force

# Build stage
FROM base AS builder
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public

USER nextjs
EXPOSE 3000
ENV PORT 3000

CMD ["npm", "start"]

Nginx Optimization

# nginx.conf for production
server {
    listen 80;
    server_name your-domain.com;

    # Enable gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json;

    # Enable Brotli compression (if available)
    brotli on;
    brotli_comp_level 6;
    brotli_types
        text/plain
        text/css
        application/javascript
        application/json
        image/svg+xml;

    # Static file caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Vary "Accept-Encoding";
    }

    # API proxy
    location /api/ {
        proxy_pass http://backend:8080/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

        # Connection pooling
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }

    # Frontend files
    location / {
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "no-cache, must-revalidate";
    }
}

Next Steps

⚠️ **GitHub.com Fallback** ⚠️