אופטימיזציית מהירות אתרים: המדריך המלא להאצת האתר שלך ב-2025
⚡ למה מהירות האתר קריטית יותר מתמיד?
בעולם שבו 53% מהמשתמשים נוטשים אתר שנטען יותר מ-3 שניות, מהירות האתר היא לא רק "nice to have" - היא גורם קריטי להצלחה.
המספרים שמוכיחים:
- 📉 שנייה אחת עיכוב = 7% ירידה בהמרות
- 🔍 Google Core Web Vitals - מהירות משפיעה ישירות על SEO
- 💰 Amazon מפסידה $1.6B בשנה על כל 100ms עיכוב
- 📱 74% מהמשתמשים במובייל נוטשים אחרי 5 שניות
- 🚀 אתרים מהירים מקבלים 2.5x יותר המרות
🎯 Core Web Vitals - המדדים שחייבים להכיר
1. LCP (Largest Contentful Paint)
היעד: < 2.5 שניות
מדד את הזמן לטעינת האלמנט הגדול ביותר בviewport
2. FID (First Input Delay) / INP (Interaction to Next Paint)
היעד: < 100ms (FID) / < 200ms (INP)
מדד את הזמן עד שהאתר מגיב לאינטראקציה הראשונה
3. CLS (Cumulative Layout Shift)
היעד: < 0.1
מדד את היציבות הויזואלית של הדף
// Measure Core Web Vitals
import { getCLS, getFID, getLCP, getTTFB, getFCP } from 'web-vitals';
function sendToAnalytics(metric) {
// Send to your analytics endpoint
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
slug: metric.id,
navigationType: metric.navigationType
});
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`
if (navigator.sendBeacon) {
navigator.sendBeacon('/analytics', body);
} else {
fetch('/analytics', {
body,
method: 'POST',
keepalive: true
});
}
}
// Track all metrics
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
getFCP(sendToAnalytics);
// Custom performance monitoring
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.init();
}
init() {
// Navigation timing
if (window.performance && window.performance.timing) {
const timing = window.performance.timing;
const navigationStart = timing.navigationStart;
this.metrics = {
// DNS lookup
dnsTime: timing.domainLookupEnd - timing.domainLookupStart,
// TCP connection
tcpTime: timing.connectEnd - timing.connectStart,
// Request time
requestTime: timing.responseStart - timing.requestStart,
// Response time
responseTime: timing.responseEnd - timing.responseStart,
// DOM processing
domProcessing: timing.domComplete - timing.domLoading,
// Load complete
loadComplete: timing.loadEventEnd - navigationStart
};
}
// Resource timing
this.analyzeResources();
}
analyzeResources() {
const resources = performance.getEntriesByType('resource');
const resourcesByType = resources.reduce((acc, resource) => {
const type = this.getResourceType(resource.name);
if (!acc[type]) acc[type] = [];
acc[type].push({
name: resource.name,
duration: resource.duration,
size: resource.transferSize,
cached: resource.transferSize === 0
});
return acc;
}, {});
this.metrics.resources = resourcesByType;
}
getResourceType(url) {
const extension = url.split('.').pop().split('?')[0];
const typeMap = {
'js': 'script',
'css': 'stylesheet',
'jpg': 'image',
'jpeg': 'image',
'png': 'image',
'gif': 'image',
'webp': 'image',
'svg': 'image',
'woff': 'font',
'woff2': 'font',
'ttf': 'font',
'eot': 'font'
};
return typeMap[extension] || 'other';
}
report() {
console.table(this.metrics);
return this.metrics;
}
}
// Initialize on load
window.addEventListener('load', () => {
const monitor = new PerformanceMonitor();
setTimeout(() => {
monitor.report();
}, 1000);
});
🖼️ אופטימיזציית תמונות - 40% מהבעיה
1. פורמטים מודרניים
<!-- Next-gen formats with fallback -->
<picture>
<!-- AVIF - best compression -->
<source
type="image/avif"
srcset="hero-320w.avif 320w,
hero-640w.avif 640w,
hero-1280w.avif 1280w,
hero-2560w.avif 2560w"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw">
<!-- WebP - good compression, wide support -->
<source
type="image/webp"
srcset="hero-320w.webp 320w,
hero-640w.webp 640w,
hero-1280w.webp 1280w,
hero-2560w.webp 2560w"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw">
<!-- JPEG fallback -->
<img
src="hero-640w.jpg"
srcset="hero-320w.jpg 320w,
hero-640w.jpg 640w,
hero-1280w.jpg 1280w,
hero-2560w.jpg 2560w"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw"
alt="Hero image"
loading="lazy"
decoding="async"
width="1280"
height="720">
</picture>
2. Lazy Loading מתקדם
// Advanced lazy loading with blur placeholder
class ProgressiveImageLoader {
constructor() {
this.images = document.querySelectorAll('[data-src]');
this.config = {
rootMargin: '50px 0px',
threshold: 0.01
};
this.init();
}
init() {
// Native lazy loading support check
if ('loading' in HTMLImageElement.prototype) {
this.images.forEach(img => {
img.loading = 'lazy';
this.loadImage(img);
});
} else {
// Fallback to Intersection Observer
this.createObserver();
}
}
createObserver() {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
observer.unobserve(entry.target);
}
});
}, this.config);
this.images.forEach(img => imageObserver.observe(img));
}
loadImage(img) {
const src = img.dataset.src;
const srcset = img.dataset.srcset;
const placeholder = img.dataset.placeholder;
// Show blur placeholder first
if (placeholder && !img.src) {
img.src = placeholder;
img.classList.add('blur-placeholder');
}
// Create new image for preloading
const tempImg = new Image();
tempImg.onload = () => {
// Apply real image
img.src = src;
if (srcset) img.srcset = srcset;
// Fade in animation
img.classList.remove('blur-placeholder');
img.classList.add('loaded');
// Clean up
delete img.dataset.src;
delete img.dataset.srcset;
};
tempImg.src = src;
if (srcset) tempImg.srcset = srcset;
}
}
// CSS for blur effect
const style = document.createElement('style');
style.textContent = `
.blur-placeholder {
filter: blur(20px);
transform: scale(1.1);
}
img {
transition: filter 0.3s, transform 0.3s;
}
img.loaded {
filter: blur(0);
transform: scale(1);
}
`;
document.head.appendChild(style);
// Initialize
document.addEventListener('DOMContentLoaded', () => {
new ProgressiveImageLoader();
});
3. Image Optimization Pipeline
// Node.js image optimization script
const sharp = require('sharp');
const glob = require('glob');
const path = require('path');
const fs = require('fs-extra');
class ImageOptimizer {
constructor(options = {}) {
this.inputDir = options.inputDir || './src/images';
this.outputDir = options.outputDir || './dist/images';
this.sizes = options.sizes || [320, 640, 768, 1024, 1280, 1920, 2560];
this.formats = options.formats || ['avif', 'webp', 'jpg'];
this.quality = options.quality || {
avif: 80,
webp: 85,
jpg: 85
};
}
async optimize() {
const images = glob.sync(`${this.inputDir}/**/*.{jpg,jpeg,png}`);
for (const imagePath of images) {
await this.processImage(imagePath);
}
}
async processImage(imagePath) {
const filename = path.basename(imagePath, path.extname(imagePath));
const relativePath = path.relative(this.inputDir, path.dirname(imagePath));
for (const format of this.formats) {
for (const width of this.sizes) {
const outputPath = path.join(
this.outputDir,
relativePath,
`${filename}-${width}w.${format}`
);
await fs.ensureDir(path.dirname(outputPath));
try {
await sharp(imagePath)
.resize(width, null, {
withoutEnlargement: true,
fit: 'inside'
})
.toFormat(format, {
quality: this.quality[format],
effort: 6 // Higher effort for better compression
})
.toFile(outputPath);
console.log(`✓ Created: ${outputPath}`);
} catch (error) {
console.error(`✗ Error processing ${imagePath}:`, error);
}
}
}
// Generate blur placeholder
await this.generatePlaceholder(imagePath, filename, relativePath);
}
async generatePlaceholder(imagePath, filename, relativePath) {
const outputPath = path.join(
this.outputDir,
relativePath,
`${filename}-placeholder.jpg`
);
const { data, info } = await sharp(imagePath)
.resize(20)
.blur(10)
.jpeg({ quality: 50 })
.toBuffer({ resolveWithObject: true });
const base64 = `data:image/jpeg;base64,${data.toString('base64')}`;
// Save base64 placeholder
await fs.writeFile(
outputPath.replace('.jpg', '.json'),
JSON.stringify({ placeholder: base64, width: info.width, height: info.height })
);
}
}
// Run optimization
const optimizer = new ImageOptimizer({
inputDir: './src/images',
outputDir: './public/images',
quality: {
avif: 75,
webp: 80,
jpg: 82
}
});
optimizer.optimize();
🚀 JavaScript Optimization
1. Code Splitting & Lazy Loading
// Webpack configuration for code splitting
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true,
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
// Split large libraries
lodash: {
test: /[\\/]node_modules[\\/]lodash/,
name: 'lodash',
priority: 20,
},
react: {
test: /[\\/]node_modules[\\/](react|react-dom)/,
name: 'react',
priority: 20,
},
},
},
runtimeChunk: 'single',
},
};
// Dynamic imports in React
import { lazy, Suspense } from 'react';
// Lazy load heavy components
const Dashboard = lazy(() =>
import(/* webpackChunkName: "dashboard" */ './Dashboard')
);
const Charts = lazy(() =>
import(/* webpackChunkName: "charts" */ './Charts')
);
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/charts" element={<Charts />} />
</Routes>
</Suspense>
);
}
// Intersection Observer for lazy loading components
function LazyComponent({ children, placeholder }) {
const [isVisible, setIsVisible] = useState(false);
const ref = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={ref}>
{isVisible ? children : placeholder}
</div>
);
}
2. Bundle Size Optimization
// Tree shaking with ES6 modules
// ❌ Bad - imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);
// ✅ Good - imports only what's needed
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
// Even better - use native alternatives
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Bundle analyzer setup
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
generateStatsFile: true,
statsFilename: 'bundle-stats.json',
}),
],
};
// Package.json scripts
{
"scripts": {
"analyze": "webpack-bundle-analyzer dist/bundle-stats.json",
"build:analyze": "npm run build && npm run analyze"
}
}
3. Preloading & Prefetching
<!-- Preload critical resources -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/app.js" as="script">
<!-- DNS Prefetch for external domains -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://analytics.google.com">
<!-- Preconnect for faster connection -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Prefetch for next page resources -->
<link rel="prefetch" href="/js/about.js">
<link rel="prefetch" href="/css/about.css">
<!-- Prerender entire page (use carefully) -->
<link rel="prerender" href="/next-page">
// Dynamic prefetching based on user behavior
class SmartPrefetcher {
constructor() {
this.prefetched = new Set();
this.init();
}
init() {
// Prefetch on hover
document.querySelectorAll('a[href]').forEach(link => {
link.addEventListener('mouseenter', () => {
this.prefetchUrl(link.href);
}, { once: true });
});
// Prefetch visible links in viewport
if ('IntersectionObserver' in window) {
const linkObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.prefetchUrl(entry.target.href);
}
});
});
document.querySelectorAll('a[href]').forEach(link => {
linkObserver.observe(link);
});
}
}
prefetchUrl(url) {
if (this.prefetched.has(url)) return;
// Only prefetch internal links
if (!url.startsWith(window.location.origin)) return;
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
this.prefetched.add(url);
}
}
// Initialize when network is idle
if ('requestIdleCallback' in window) {
requestIdleCallback(() => new SmartPrefetcher());
} else {
setTimeout(() => new SmartPrefetcher(), 2000);
}
🎨 CSS Optimization
1. Critical CSS Extraction
// Generate critical CSS with Penthouse
const penthouse = require('penthouse');
const fs = require('fs');
async function generateCriticalCSS() {
const critical = await penthouse({
url: 'http://localhost:3000',
css: './dist/styles.css',
width: 1300,
height: 900,
keepLargerMediaQueries: false,
screenshots: {
basePath: './critical-css-screenshots',
}
});
fs.writeFileSync('./dist/critical.css', critical);
}
// Inline critical CSS in HTML
function inlineCriticalCSS(html, criticalCSS) {
return html.replace(
'</head>',
`<style>${criticalCSS}</style></head>`
);
}
// Load non-critical CSS asynchronously
<!-- Inline critical CSS -->
<style>
/* Critical CSS here */
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
.header { background: #333; color: white; padding: 1rem; }
/* ... more critical styles ... */
</style>
<!-- Load non-critical CSS asynchronously -->
<link rel="preload" href="/css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
2. CSS-in-JS Optimization
// Emotion with SSR and critical extraction
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import createCache from '@emotion/cache';
// Server-side
const cache = createCache({ key: 'css' });
const { extractCriticalToChunks, constructStyleTagsFromChunks } =
createEmotionServer(cache);
function renderApp(req, res) {
const html = ReactDOMServer.renderToString(
<CacheProvider value={cache}>
<App />
</CacheProvider>
);
const emotionChunks = extractCriticalToChunks(html);
const emotionCss = constructStyleTagsFromChunks(emotionChunks);
res.send(`
<!DOCTYPE html>
<html>
<head>
${emotionCss}
</head>
<body>
<div id="root">${html}</div>
<script src="/app.js" defer></script>
</body>
</html>
`);
}
⚙️ Server & Infrastructure Optimization
1. Caching Strategy
// Service Worker with advanced caching
const CACHE_VERSION = 'v1';
const CACHE_NAMES = {
static: `static-${CACHE_VERSION}`,
dynamic: `dynamic-${CACHE_VERSION}`,
images: `images-${CACHE_VERSION}`
};
// Cache strategies
const cacheStrategies = {
// Network first, fall back to cache
networkFirst: async (request) => {
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAMES.dynamic);
cache.put(request, response.clone());
return response;
} catch (error) {
return caches.match(request);
}
},
// Cache first, fall back to network
cacheFirst: async (request) => {
const cached = await caches.match(request);
if (cached) return cached;
const response = await fetch(request);
const cache = await caches.open(CACHE_NAMES.static);
cache.put(request, response.clone());
return response;
},
// Stale while revalidate
staleWhileRevalidate: async (request) => {
const cached = await caches.match(request);
const fetchPromise = fetch(request).then(response => {
const cache = caches.open(CACHE_NAMES.dynamic);
cache.then(c => c.put(request, response.clone()));
return response;
});
return cached || fetchPromise;
}
};
// Apply strategies based on request type
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// Static assets - cache first
if (url.pathname.match(/\.(js|css|woff2?)$/)) {
event.respondWith(cacheStrategies.cacheFirst(request));
}
// Images - stale while revalidate
else if (url.pathname.match(/\.(png|jpg|jpeg|svg|webp)$/)) {
event.respondWith(cacheStrategies.staleWhileRevalidate(request));
}
// API calls - network first
else if (url.pathname.startsWith('/api/')) {
event.respondWith(cacheStrategies.networkFirst(request));
}
});
2. HTTP/2 & HTTP/3 Configuration
# Nginx configuration for HTTP/2 and HTTP/3
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 quic reuseport; # HTTP/3
listen [::]:443 quic reuseport;
server_name example.com;
# HTTP/3 advertisement
add_header Alt-Svc 'h3=":443"; ma=86400';
# SSL configuration
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.3;
# Enable OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# HTTP/2 Push
http2_push_preload on;
# Compression
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml application/atom+xml image/svg+xml
text/x-js text/x-cross-domain-policy application/x-font-ttf
application/x-font-opentype application/vnd.ms-fontobject
image/x-icon;
# Brotli compression (better than gzip)
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
# Cache headers
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
}
3. CDN Configuration
// Cloudflare Workers for edge optimization
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
// Image optimization at edge
if (url.pathname.match(/\.(jpg|jpeg|png|gif|webp)$/)) {
return handleImageRequest(request, url);
}
// Cache HTML at edge with personalization
if (url.pathname.endsWith('.html') || url.pathname === '/') {
return handleHTMLRequest(request);
}
// Default response
return fetch(request);
}
async function handleImageRequest(request, url) {
const accept = request.headers.get('Accept');
const cache = caches.default;
// Modify URL for responsive images
const width = url.searchParams.get('w');
const quality = url.searchParams.get('q') || '85';
const format = accept.includes('image/avif') ? 'avif'
: accept.includes('image/webp') ? 'webp'
: 'jpg';
const imageURL = `https://imageservice.com/${url.pathname}?w=${width}&q=${quality}&format=${format}`;
// Check cache
let response = await cache.match(imageURL);
if (!response) {
response = await fetch(imageURL);
// Cache for 1 year
response = new Response(response.body, response);
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
// Store in cache
event.waitUntil(cache.put(imageURL, response.clone()));
}
return response;
}
📊 Database & API Optimization
1. GraphQL with DataLoader
// Avoid N+1 queries with DataLoader
const DataLoader = require('dataloader');
class UserLoader {
constructor(db) {
this.db = db;
this.loader = new DataLoader(this.batchLoadUsers.bind(this));
}
async batchLoadUsers(userIds) {
const users = await this.db.query(
'SELECT * FROM users WHERE id IN (?)',
[userIds]
);
// Map results back to original order
const userMap = {};
users.forEach(user => {
userMap[user.id] = user;
});
return userIds.map(id => userMap[id] || null);
}
async load(userId) {
return this.loader.load(userId);
}
}
// GraphQL resolver with caching
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return dataSources.userLoader.load(id);
},
posts: async (_, { limit = 10, offset = 0 }, { dataSources, cache }) => {
const cacheKey = `posts:${limit}:${offset}`;
// Check Redis cache
const cached = await cache.get(cacheKey);
if (cached) return JSON.parse(cached);
// Fetch from database
const posts = await dataSources.postAPI.getPosts(limit, offset);
// Cache for 5 minutes
await cache.set(cacheKey, JSON.stringify(posts), 'EX', 300);
return posts;
}
},
User: {
posts: async (user, _, { dataSources }) => {
return dataSources.postLoader.loadByUserId(user.id);
}
}
};
2. API Response Optimization
// Compression and streaming responses
const compression = require('compression');
const { pipeline } = require('stream');
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
level: 6,
threshold: 1024
}));
// Stream large responses
app.get('/api/large-dataset', async (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Transfer-Encoding', 'chunked');
const stream = new Readable({
async read() {
const batch = await getNextBatch();
if (batch) {
this.push(JSON.stringify(batch) + '\n');
} else {
this.push(null);
}
}
});
pipeline(stream, res, (err) => {
if (err) console.error('Pipeline failed:', err);
});
});
// Pagination with cursor
app.get('/api/items', async (req, res) => {
const { cursor, limit = 20 } = req.query;
const query = cursor
? 'SELECT * FROM items WHERE id > ? ORDER BY id LIMIT ?'
: 'SELECT * FROM items ORDER BY id LIMIT ?';
const params = cursor ? [cursor, limit + 1] : [limit + 1];
const items = await db.query(query, params);
const hasMore = items.length > limit;
const data = hasMore ? items.slice(0, -1) : items;
const nextCursor = hasMore ? data[data.length - 1].id : null;
res.json({
data,
pagination: {
hasMore,
nextCursor
}
});
});
🔧 Performance Testing & Monitoring
1. Automated Performance Testing
// Lighthouse CI configuration
module.exports = {
ci: {
collect: {
urls: [
'http://localhost:3000/',
'http://localhost:3000/products',
'http://localhost:3000/about'
],
numberOfRuns: 3,
settings: {
preset: 'desktop',
throttling: {
cpuSlowdownMultiplier: 1,
},
},
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['warn', { minScore: 0.9 }],
'categories:best-practices': ['warn', { minScore: 0.9 }],
'categories:seo': ['warn', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 3000 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 300 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};
// Performance budget with Webpack
module.exports = {
performance: {
hints: 'error',
maxEntrypointSize: 250000, // 250kb
maxAssetSize: 100000, // 100kb
assetFilter: function(assetFilename) {
return !assetFilename.endsWith('.map');
}
}
};
2. Real User Monitoring (RUM)
// Custom RUM implementation
class RealUserMonitoring {
constructor(endpoint) {
this.endpoint = endpoint;
this.metrics = {};
this.init();
}
init() {
// Performance Observer for Core Web Vitals
if ('PerformanceObserver' in window) {
// LCP
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.renderTime || lastEntry.loadTime;
}).observe({ entryTypes: ['largest-contentful-paint'] });
// FID
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
this.metrics.fid = entry.processingStart - entry.startTime;
});
}).observe({ entryTypes: ['first-input'] });
// CLS
let clsValue = 0;
let clsEntries = [];
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
const firstSessionEntry = clsEntries[0];
const lastSessionEntry = clsEntries[clsEntries.length - 1];
if (entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000) {
clsEntries.push(entry);
clsValue += entry.value;
} else {
clsEntries = [entry];
clsValue = entry.value;
}
}
}
this.metrics.cls = clsValue;
}).observe({ entryTypes: ['layout-shift'] });
}
// Send metrics on page unload
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
this.sendMetrics();
}
});
}
sendMetrics() {
const data = {
url: window.location.href,
metrics: this.metrics,
connection: navigator.connection ? {
effectiveType: navigator.connection.effectiveType,
downlink: navigator.connection.downlink,
rtt: navigator.connection.rtt
} : null,
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
timestamp: Date.now()
};
// Use sendBeacon for reliability
navigator.sendBeacon(this.endpoint, JSON.stringify(data));
}
}
// Initialize RUM
new RealUserMonitoring('/api/rum');
✅ Performance Checklist
Critical Path Optimization:
- Inline critical CSS
- Defer non-critical CSS
- Async/defer JavaScript
- Preload key resources
- Optimize web fonts
Images:
- Use next-gen formats (AVIF, WebP)
- Implement responsive images
- Lazy load below-fold images
- Optimize image sizes
- Use image CDN
JavaScript:
- Code splitting
- Tree shaking
- Minification
- Remove unused code
- Use production builds
Network:
- Enable compression (Brotli/Gzip)
- Use HTTP/2 or HTTP/3
- Implement caching strategy
- Use CDN
- Minimize redirects
Server:
- Database query optimization
- API response caching
- Enable keep-alive
- Optimize TTFB
- Use edge computing
Monitoring:
- Set up RUM
- Track Core Web Vitals
- Create performance budget
- Regular performance audits
- A/B test optimizations
🚀 סיכום והמלצות
Quick Wins (יישום מיידי):
- הפעל compression - 70% הקטנת גודל
- אופטימיזציית תמונות - 40% שיפור במהירות
- Browser caching - 50% מהיר יותר לvisitors חוזרים
- Minify CSS/JS - 20-30% הקטנת קבצים
Medium Term (1-2 שבועות):
- Code splitting - טעינה מהירה יותר
- CDN implementation - שיפור גלובלי
- Critical CSS - מהירות תצוגה ראשונית
- Image lazy loading - חיסכון ב-bandwidth
Long Term (1-3 חודשים):
- Server-side rendering - SEO וביצועים
- PWA features - offline functionality
- Edge computing - minimal latency
- Micro-frontends - independent scaling
זכור: כל 100ms שיפור = 1% עלייה בהמרות! ⚡