mirror of https://github.com/ColorlibHQ/gentelella
686 lines
16 KiB
Markdown
686 lines
16 KiB
Markdown
---
|
|
layout: default
|
|
title: Performance Guide
|
|
nav_order: 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:
|
|
|
|
```javascript
|
|
// 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)
|
|
```javascript
|
|
// Only loads when chart elements are detected
|
|
if (document.querySelector('.chart-container')) {
|
|
const charts = await import('./modules/charts.js');
|
|
charts.initializeCharts();
|
|
}
|
|
```
|
|
|
|
**Forms Module** (200KB)
|
|
```javascript
|
|
// 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**
|
|
```javascript
|
|
// Only loads when DataTables are needed
|
|
if (document.querySelector('.datatable')) {
|
|
const tables = await import('./modules/tables.js');
|
|
tables.initializeTables();
|
|
}
|
|
```
|
|
|
|
### Module Loading Strategy
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// ❌ 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
|
|
```html
|
|
<!-- 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
|
|
```html
|
|
<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
|
|
```html
|
|
<!-- 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">
|
|
```
|
|
|
|
```javascript
|
|
// 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
|
|
```html
|
|
<!-- 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
|
|
```css
|
|
/* 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
|
|
```javascript
|
|
// 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
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// vite.config.js - CDN configuration
|
|
export default defineConfig({
|
|
build: {
|
|
rollupOptions: {
|
|
external: ['jquery', 'bootstrap'],
|
|
output: {
|
|
globals: {
|
|
jquery: 'jQuery',
|
|
bootstrap: 'bootstrap'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
```html
|
|
<!-- 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
|
|
```javascript
|
|
// ❌ 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
|
|
```javascript
|
|
// ❌ 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
|
|
```javascript
|
|
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
|
|
```javascript
|
|
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
|
|
```javascript
|
|
// 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
|
|
```javascript
|
|
// 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
|
|
```bash
|
|
# Analyze your bundles
|
|
npm run build:analyze
|
|
```
|
|
|
|
```javascript
|
|
// scripts/analyze.js
|
|
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
process.env.ANALYZE && new BundleAnalyzerPlugin()
|
|
].filter(Boolean)
|
|
});
|
|
```
|
|
|
|
#### Lighthouse CI
|
|
```yaml
|
|
# .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
|
|
```html
|
|
<!-- 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
|
|
```javascript
|
|
// 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
|
|
```html
|
|
<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
|
|
```html
|
|
<!-- 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. |