gentelella/docs/performance.md

16 KiB

layout title nav_order
default Performance Guide 5

Performance Optimization

{: .no_toc }

Complete guide to optimizing Gentelella Admin Template for maximum performance {: .fs-6 .fw-300 }

Table of contents

{: .no_toc .text-delta }

  1. TOC {:toc}

Performance Overview

Gentelella v2.0 includes significant performance improvements over the original version:

Performance Metrics

Metric v1.0 v2.0 Improvement
Initial Bundle Size 779 KB 79 KB 90% smaller
Total Page Load 1.3 MB 770 KB 40% reduction
First Contentful Paint 2.1s 0.8s 62% faster
Time to Interactive 3.5s 1.2s 66% faster
Largest Contentful Paint 2.8s 1.1s 61% faster
Cumulative Layout Shift 0.15 0.03 80% improvement

Smart Loading System

Core vs. Module Architecture

The template uses a two-tier loading system:

Core Bundle (79KB) - Always Loaded

Essential functionality that every page needs:

// src/main-core.js
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
import './js/custom.min.js';

// Initialize tooltips and popovers
document.addEventListener('DOMContentLoaded', function() {
  // Bootstrap components initialization
  const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
  tooltips.forEach(tooltip => new bootstrap.Tooltip(tooltip));
  
  const popovers = document.querySelectorAll('[data-bs-toggle="popover"]');
  popovers.forEach(popover => new bootstrap.Popover(popover));
});

Conditional Modules - Loaded on Demand

Charts Module (219KB)

// Only loads when chart elements are detected
if (document.querySelector('.chart-container')) {
  const charts = await import('./modules/charts.js');
  charts.initializeCharts();
}

Forms Module (200KB)

// Only loads on pages with enhanced forms
if (document.querySelector('.select2, .datepicker, .form-wizard')) {
  const forms = await import('./modules/forms.js');
  forms.initializeForms();
}

Tables Module

// Only loads when DataTables are needed
if (document.querySelector('.datatable')) {
  const tables = await import('./modules/tables.js');
  tables.initializeTables();
}

Module Loading Strategy

// Smart module detection and loading
export async function loadRequiredModules() {
  const modules = [];
  
  // Check for chart requirements
  if (document.querySelector('canvas, .morris-chart, .sparkline')) {
    modules.push(import('./modules/charts.js'));
  }
  
  // Check for form enhancements
  if (document.querySelector('.select2, .datepicker, .ion-range-slider')) {
    modules.push(import('./modules/forms.js'));
  }
  
  // Check for table features
  if (document.querySelector('.datatable, table[data-table]')) {
    modules.push(import('./modules/tables.js'));
  }
  
  // Check for dashboard widgets
  if (document.querySelector('.dashboard-widget, .tile_count')) {
    modules.push(import('./modules/dashboard.js'));
  }
  
  // Load all required modules in parallel
  const loadedModules = await Promise.all(modules);
  
  // Initialize each module
  loadedModules.forEach(module => {
    if (module.initialize) {
      module.initialize();
    }
  });
}

Bundle Optimization

Manual Chunk Splitting

The Vite configuration includes optimized chunk splitting:

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Core vendor libraries (loaded on every page)
          'vendor-core': [
            'bootstrap',
            '@popperjs/core'
          ],
          
          // Chart libraries (loaded only when needed)
          'vendor-charts': [
            'chart.js',
            'morris.js',
            'gauge.js',
            'jquery-sparkline'
          ],
          
          // Form enhancement libraries
          'vendor-forms': [
            'select2',
            'tempus-dominus',
            'ion-rangeslider',
            'switchery'
          ],
          
          // Table functionality
          'vendor-tables': [
            'datatables.net',
            'datatables.net-bs5',
            'datatables.net-responsive',
            'datatables.net-buttons'
          ],
          
          // Utility libraries
          'vendor-utils': [
            'dayjs',
            'nprogress',
            'autosize'
          ]
        }
      }
    }
  }
});

Tree Shaking

Import only what you need:

// ❌ Bad - imports entire library
import * as dayjs from 'dayjs';

// ✅ Good - imports only specific functions
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';

// ❌ Bad - imports entire Chart.js
import Chart from 'chart.js';

// ✅ Good - imports only needed components
import {
  Chart,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
} from 'chart.js';

Chart.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend
);

Asset Optimization

Image Optimization

Responsive Images

<!-- Use responsive images with srcset -->
<img src="images/thumb-400.jpg" 
     srcset="images/thumb-400.jpg 400w,
             images/thumb-800.jpg 800w,
             images/thumb-1200.jpg 1200w"
     sizes="(max-width: 768px) 100vw,
            (max-width: 1200px) 50vw,
            33vw"
     alt="Dashboard preview"
     loading="lazy">

WebP Format with Fallback

<picture>
  <source srcset="images/dashboard.webp" type="image/webp">
  <source srcset="images/dashboard.jpg" type="image/jpeg">
  <img src="images/dashboard.jpg" alt="Dashboard" loading="lazy">
</picture>

Lazy Loading

<!-- Native lazy loading -->
<img src="images/placeholder.jpg" 
     data-src="images/actual-image.jpg"
     loading="lazy"
     alt="Description">

<!-- Intersection Observer approach -->
<img class="lazy" 
     src="images/placeholder.jpg" 
     data-src="images/actual-image.jpg"
     alt="Description">
// Lazy loading implementation
const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.remove('lazy');
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('img[data-src]').forEach(img => {
  imageObserver.observe(img);
});

Font Optimization

Font Loading Strategy

<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/roboto-regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/roboto-bold.woff2" as="font" type="font/woff2" crossorigin>

<!-- Font display swap for better performance -->
<style>
@font-face {
  font-family: 'Roboto';
  src: url('/fonts/roboto-regular.woff2') format('woff2');
  font-display: swap;
  font-weight: 400;
}
</style>

Subset Fonts

/* Load only the characters you need */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&text=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');

Caching Strategies

Browser Caching

Vite Asset Hashing

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // Add hash to filenames for cache busting
        entryFileNames: `assets/[name].[hash].js`,
        chunkFileNames: `assets/[name].[hash].js`,
        assetFileNames: `assets/[name].[hash].[ext]`
      }
    }
  }
});

Service Worker Implementation

// sw.js - Service Worker for caching
const CACHE_NAME = 'gentelella-v2.0.0';
const urlsToCache = [
  '/',
  '/assets/vendor-core.js',
  '/assets/main-core.js',
  '/assets/main.css',
  '/images/favicon.ico'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Return cached version or fetch from network
        return response || fetch(event.request);
      })
  );
});

CDN Integration

// vite.config.js - CDN configuration
export default defineConfig({
  build: {
    rollupOptions: {
      external: ['jquery', 'bootstrap'],
      output: {
        globals: {
          jquery: 'jQuery',
          bootstrap: 'bootstrap'
        }
      }
    }
  }
});
<!-- Load popular libraries from CDN -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js"></script>

Runtime Performance

Efficient DOM Manipulation

Batch DOM Updates

// ❌ Bad - multiple reflows
function updateMultipleElements(data) {
  data.forEach(item => {
    const element = document.getElementById(item.id);
    element.style.width = item.width + 'px';
    element.style.height = item.height + 'px';
    element.textContent = item.text;
  });
}

// ✅ Good - single reflow
function updateMultipleElements(data) {
  const fragment = document.createDocumentFragment();
  
  data.forEach(item => {
    const element = document.getElementById(item.id).cloneNode(true);
    element.style.width = item.width + 'px';
    element.style.height = item.height + 'px';
    element.textContent = item.text;
    fragment.appendChild(element);
  });
  
  document.body.appendChild(fragment);
}

Event Delegation

// ❌ Bad - multiple event listeners
document.querySelectorAll('.btn').forEach(btn => {
  btn.addEventListener('click', handleClick);
});

// ✅ Good - single delegated listener
document.addEventListener('click', function(e) {
  if (e.target.classList.contains('btn')) {
    handleClick(e);
  }
});

Memory Management

Cleanup Event Listeners

class Component {
  constructor(element) {
    this.element = element;
    this.handleClick = this.handleClick.bind(this);
    this.element.addEventListener('click', this.handleClick);
  }
  
  destroy() {
    // Clean up event listeners
    this.element.removeEventListener('click', this.handleClick);
    this.element = null;
  }
  
  handleClick(e) {
    // Handle click
  }
}

Debounce Expensive Operations

function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// Usage for search
const debouncedSearch = debounce(performSearch, 300);
document.getElementById('search').addEventListener('input', debouncedSearch);

Monitoring and Analysis

Performance Monitoring

Core Web Vitals

// Monitor Core Web Vitals
import {getCLS, getFID, getFCP, getLCP, getTTFB} from 'web-vitals';

function sendToAnalytics(metric) {
  // Send to your analytics service
  console.log(metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

Performance Observer

// Monitor long tasks
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('Long task detected:', entry);
  }
});

observer.observe({entryTypes: ['longtask']});

// Monitor resource loading
const resourceObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 1000) {
      console.log('Slow resource:', entry.name, entry.duration);
    }
  }
});

resourceObserver.observe({entryTypes: ['resource']});

Bundle Analysis

Webpack Bundle Analyzer

# Analyze your bundles
npm run build:analyze
// scripts/analyze.js
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';

export default defineConfig({
  plugins: [
    process.env.ANALYZE && new BundleAnalyzerPlugin()
  ].filter(Boolean)
});

Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      - run: npm install
      - run: npm run build
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v8
        with:
          configPath: './lighthouserc.json'

Performance Checklist

Development Phase

  • Code Splitting: Implement route-based and component-based splitting
  • Tree Shaking: Import only needed functions and components
  • Module Loading: Use dynamic imports for non-critical code
  • Bundle Analysis: Regularly analyze bundle sizes
  • Dead Code: Remove unused CSS and JavaScript

Asset Optimization

  • Images: Optimize, use WebP format, implement lazy loading
  • Fonts: Subset fonts, use font-display: swap
  • Icons: Use icon fonts or SVG sprites
  • Compression: Enable gzip/brotli compression
  • Minification: Minify CSS, JavaScript, and HTML

Caching Strategy

  • Browser Caching: Set appropriate cache headers
  • CDN: Use CDN for static assets
  • Service Worker: Implement for offline functionality
  • Versioning: Use file hashing for cache busting

Runtime Performance

  • Event Delegation: Use for dynamic content
  • Debouncing: Implement for expensive operations
  • Memory Leaks: Clean up event listeners and timers
  • DOM Manipulation: Batch updates and use DocumentFragment

Monitoring

  • Core Web Vitals: Monitor LCP, FID, CLS
  • Performance API: Track loading times
  • Error Tracking: Monitor JavaScript errors
  • User Experience: Track real user metrics

Advanced Optimization Techniques

Preloading Strategies

Module Preloading

<!-- Preload critical modules -->
<link rel="modulepreload" href="/assets/vendor-core.js">
<link rel="modulepreload" href="/assets/main-core.js">

<!-- Prefetch likely-needed modules -->
<link rel="prefetch" href="/assets/vendor-charts.js">
<link rel="prefetch" href="/assets/vendor-forms.js">

Predictive Loading

// Preload modules based on user behavior
const observeNavigation = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const href = entry.target.getAttribute('href');
      
      // Preload likely modules for the target page
      if (href.includes('charts')) {
        import('./modules/charts.js');
      } else if (href.includes('forms')) {
        import('./modules/forms.js');
      }
    }
  });
});

// Observe navigation links
document.querySelectorAll('a[href]').forEach(link => {
  observeNavigation.observe(link);
});

Critical Path Optimization

Critical CSS Inlining

<style>
  /* Inline critical CSS for above-the-fold content */
  .header, .sidebar, .main-content { /* critical styles */ }
</style>

<!-- Load full CSS asynchronously -->
<link rel="preload" href="/assets/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

Resource Hints

<!-- DNS prefetch for external resources -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">

<!-- Preconnect to critical third-party origins -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

Next Steps

  • [Deployment Guide]({{ site.baseurl }}/docs/deployment/) - Deploy optimized builds
  • [Monitoring Guide]({{ site.baseurl }}/docs/monitoring/) - Set up performance monitoring
  • [Troubleshooting]({{ site.baseurl }}/docs/troubleshooting/) - Solve performance issues

{: .highlight } 💡 Pro Tip: Use the npm run optimize command to analyze your current bundle and get personalized optimization recommendations based on your specific usage patterns.