Core Web Vitals Optimization
Table of Contents
Core Web Vitals Optimization
Performance-First Approach for Modern Web
Jankx 2.0 được thiết kế để tối ưu hóa Core Web Vitals (LCP, FID, CLS) và đảm bảo trải nghiệm người dùng tốt nhất.
📊 Core Web Vitals Targets
Performance Targets
- LCP (Largest Contentful Paint): < 2.5s
- FID (First Input Delay): < 100ms
- CLS (Cumulative Layout Shift): < 0.1
- TTFB (Time to First Byte): < 600ms
Monitoring Setup
class CoreWebVitalsMonitor
{
private $metrics = [];
public function trackLCP(float $value): void
{
$this->metrics['lcp'] = $value;
$this->logMetric('lcp', $value);
}
public function trackFID(float $value): void
{
$this->metrics['fid'] = $value;
$this->logMetric('fid', $value);
}
public function trackCLS(float $value): void
{
$this->metrics['cls'] = $value;
$this->logMetric('cls', $value);
}
public function getMetrics(): array
{
return $this->metrics;
}
}
🚀 LCP Optimization
Critical Resource Loading
class CriticalResourceLoader
{
public function loadCriticalCSS(): void
{
$criticalCSS = $this->generateCriticalCSS();
$this->inlineCSS($criticalCSS);
}
public function preloadCriticalResources(): void
{
// Preload hero image
add_action('wp_head', function() {
echo '<link rel="preload" as="image" href="' . esc_url(get_theme_file_uri('assets/images/hero.jpg')) . '">';
});
// Preload critical fonts
add_action('wp_head', function() {
echo '<link rel="preload" href="' . esc_url(get_theme_file_uri('assets/fonts/main.woff2')) . '" as="font" type="font/woff2" crossorigin>';
});
}
private function generateCriticalCSS(): string
{
$criticalSelectors = [
'.hero-section',
'.main-navigation',
'.site-header',
'.hero-title',
'.hero-description'
];
return $this->extractCSSForSelectors($criticalSelectors);
}
}
Image Optimization
class ImageOptimizer
{
public function optimizeHeroImage(string $imageUrl): string
{
// Generate WebP version
$webpUrl = $this->generateWebP($imageUrl);
// Generate responsive images
$responsiveImages = $this->generateResponsiveImages($imageUrl);
return $this->buildPictureElement($imageUrl, $webpUrl, $responsiveImages);
}
public function lazyLoadImages(): void
{
add_filter('wp_get_attachment_image_attributes', function($attr, $attachment) {
$attr['loading'] = 'lazy';
$attr['decoding'] = 'async';
return $attr;
}, 10, 2);
}
private function buildPictureElement(string $original, string $webp, array $responsive): string
{
return sprintf(
'<picture>
<source srcset="%s" type="image/webp">
<source srcset="%s" type="image/jpeg">
<img src="%s" alt="" loading="lazy" decoding="async">
</picture>',
$webp,
$responsive['srcset'],
$original
);
}
}
⚡ FID Optimization
JavaScript Optimization
class JavaScriptOptimizer
{
public function deferNonCriticalJS(): void
{
add_filter('script_loader_tag', function($tag, $handle) {
if ($this->isNonCriticalScript($handle)) {
return str_replace('<script ', '<script defer ', $tag);
}
return $tag;
}, 10, 2);
}
public function inlineCriticalJS(): void
{
$criticalJS = $this->extractCriticalJS();
add_action('wp_head', function() use ($criticalJS) {
echo '<script>' . $criticalJS . '</script>';
});
}
private function isNonCriticalScript(string $handle): bool
{
$nonCriticalScripts = [
'jankx-analytics',
'jankx-social-share',
'jankx-comments'
];
return in_array($handle, $nonCriticalScripts);
}
}
Event Handler Optimization
class EventHandlerOptimizer
{
public function optimizeEventHandlers(): void
{
// Use passive event listeners
add_action('wp_footer', function() {
?>
<script>
document.addEventListener('scroll', function() {
// Scroll handling
}, { passive: true });
document.addEventListener('touchstart', function() {
// Touch handling
}, { passive: true });
</script>
<?php
});
}
public function debounceEventHandlers(): void
{
add_action('wp_footer', function() {
?>
<script>
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Debounce scroll events
const debouncedScrollHandler = debounce(function() {
// Handle scroll
}, 16);
window.addEventListener('scroll', debouncedScrollHandler);
</script>
<?php
});
}
}
📐 CLS Optimization
Layout Stability
class LayoutStabilityOptimizer
{
public function reserveSpaceForImages(): void
{
add_filter('wp_get_attachment_image_attributes', function($attr, $attachment) {
$metadata = wp_get_attachment_metadata($attachment->ID);
if ($metadata) {
$aspectRatio = $metadata['height'] / $metadata['width'];
$attr['style'] = 'aspect-ratio: ' . $aspectRatio . ';';
}
return $attr;
}, 10, 2);
}
public function reserveSpaceForFonts(): void
{
add_action('wp_head', function() {
?>
<style>
/* Reserve space for custom fonts */
.font-loaded {
font-family: 'Custom Font', sans-serif;
}
.font-loading {
font-family: 'Fallback Font', sans-serif;
font-size: 1.2em; /* Adjust for font metrics */
}
</style>
<?php
});
}
public function preventLayoutShift(): void
{
add_action('wp_head', function() {
?>
<style>
/* Prevent layout shift for dynamic content */
.dynamic-content {
min-height: 200px; /* Reserve space */
}
.advertisement {
min-height: 250px; /* Reserve space for ads */
}
.social-share {
min-height: 40px; /* Reserve space for social buttons */
}
</style>
<?php
});
}
}
Font Loading Strategy
class FontLoadingOptimizer
{
public function optimizeFontLoading(): void
{
add_action('wp_head', function() {
?>
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<style>
/* Font display swap for better performance */
@font-face {
font-family: 'Custom Font';
src: url('/fonts/main.woff2') format('woff2');
font-display: swap;
}
/* Font fallback with similar metrics */
.font-fallback {
font-family: 'Arial', sans-serif;
font-size: 1.1em; /* Adjust for similar metrics */
}
</style>
<?php
});
}
public function implementFontLoading(): void
{
add_action('wp_footer', function() {
?>
<script>
// Font loading with fallback
if ('fonts' in document) {
document.fonts.load('1em Custom Font').then(function() {
document.documentElement.classList.add('font-loaded');
}).catch(function() {
document.documentElement.classList.add('font-fallback');
});
}
</script>
<?php
});
}
}
🔄 Resource Loading Strategy
Critical Path Optimization
class CriticalPathOptimizer
{
public function inlineCriticalResources(): void
{
// Inline critical CSS
$criticalCSS = $this->extractCriticalCSS();
add_action('wp_head', function() use ($criticalCSS) {
echo '<style>' . $criticalCSS . '</style>';
});
// Inline critical JavaScript
$criticalJS = $this->extractCriticalJS();
add_action('wp_head', function() use ($criticalJS) {
echo '<script>' . $criticalJS . '</script>';
});
}
public function deferNonCriticalResources(): void
{
// Defer non-critical CSS
add_filter('style_loader_tag', function($tag, $handle) {
if ($this->isNonCriticalStyle($handle)) {
return str_replace('<link ', '<link media="print" onload="this.media=\'all\'" ', $tag);
}
return $tag;
}, 10, 2);
// Defer non-critical JavaScript
add_filter('script_loader_tag', function($tag, $handle) {
if ($this->isNonCriticalScript($handle)) {
return str_replace('<script ', '<script defer ', $tag);
}
return $tag;
}, 10, 2);
}
}
Asset Loading Optimization
class AssetLoadingOptimizer
{
public function optimizeCSSLoading(): void
{
// Combine and minify CSS
$this->combineCSS();
// Remove unused CSS
$this->purgeUnusedCSS();
// Compress CSS
$this->compressCSS();
}
public function optimizeJSLoading(): void
{
// Bundle JavaScript
$this->bundleJavaScript();
// Minify JavaScript
$this->minifyJavaScript();
// Tree shake unused code
$this->treeShakeJavaScript();
}
public function optimizeImageLoading(): void
{
// Generate WebP images
$this->generateWebPImages();
// Generate responsive images
$this->generateResponsiveImages();
// Optimize image compression
$this->optimizeImageCompression();
}
}
📈 Performance Monitoring
Real User Monitoring (RUM)
class PerformanceMonitor
{
public function trackCoreWebVitals(): void
{
add_action('wp_footer', function() {
?>
<script>
// Track LCP
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.entryType === 'largest-contentful-paint') {
// Send to analytics
this.sendMetric('lcp', entry.startTime);
}
}
}).observe({entryTypes: ['largest-contentful-paint']});
// Track FID
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.entryType === 'first-input') {
// Send to analytics
this.sendMetric('fid', entry.processingStart - entry.startTime);
}
}
}).observe({entryTypes: ['first-input']});
// Track CLS
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
// Send to analytics
this.sendMetric('cls', clsValue);
}
}
}).observe({entryTypes: ['layout-shift']});
</script>
<?php
});
}
private function sendMetric(string $metric, float $value): void
{
// Send to analytics service
if ('gtag' in window) {
gtag('event', 'web_vitals', {
event_category: 'Web Vitals',
event_label: metric,
value: Math.round(value),
non_interaction: true
});
}
}
}
Performance Budget
class PerformanceBudget
{
private $budgets = [
'lcp' => 2500, // 2.5 seconds
'fid' => 100, // 100 milliseconds
'cls' => 0.1, // 0.1
'ttfb' => 600, // 600 milliseconds
'js_size' => 300000, // 300KB
'css_size' => 50000, // 50KB
'image_size' => 1000000 // 1MB
];
public function checkPerformanceBudget(): array
{
$violations = [];
foreach ($this->budgets as $metric => $budget) {
$currentValue = $this->getCurrentValue($metric);
if ($currentValue > $budget) {
$violations[$metric] = [
'budget' => $budget,
'actual' => $currentValue,
'excess' => $currentValue - $budget
];
}
}
return $violations;
}
public function generateReport(): string
{
$violations = $this->checkPerformanceBudget();
if (empty($violations)) {
return '✅ All performance budgets met';
}
$report = "❌ Performance budget violations:\n";
foreach ($violations as $metric => $data) {
$report .= sprintf(
"- %s: %s (budget: %s, excess: %s)\n",
strtoupper($metric),
$data['actual'],
$data['budget'],
$data['excess']
);
}
return $report;
}
}
🛠 Optimization Tools
Build Process
// package.json
{
"scripts": {
"build": "npm run build:css && npm run build:js && npm run optimize:images",
"build:css": "postcss src/css/main.css -o dist/css/main.min.css",
"build:js": "webpack --mode=production",
"optimize:images": "imagemin src/images/* --out-dir=dist/images",
"analyze": "webpack-bundle-analyzer dist/js/bundle.js",
"lighthouse": "lighthouse https://example.com --output=html --output-path=./lighthouse-report.html"
},
"devDependencies": {
"postcss": "^8.4.0",
"postcss-purgecss": "^5.0.0",
"webpack": "^5.0.0",
"imagemin": "^8.0.0",
"lighthouse": "^10.0.0"
}
}
Webpack Configuration
// webpack.config.js
module.exports = {
mode: 'production',
entry: './src/js/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist/js')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
}
}
})
]
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
};
Next: Asset Management | Caching Strategy | Lazy Loading |