360 lines
12 KiB
TypeScript
360 lines
12 KiB
TypeScript
|
|
// 性能优化配置
|
|||
|
|
export const performanceConfig = {
|
|||
|
|
// 缓存配置
|
|||
|
|
cache: {
|
|||
|
|
// API 缓存时间 (秒)
|
|||
|
|
apiCacheDuration: 3600, // 1小时
|
|||
|
|
// ISR 重新验证时间 (秒)
|
|||
|
|
isrRevalidate: 3600, // 1小时
|
|||
|
|
// 静态资源缓存时间
|
|||
|
|
staticAssetsCacheDuration: 86400, // 24小时
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 预加载配置
|
|||
|
|
prefetch: {
|
|||
|
|
// 关键页面列表
|
|||
|
|
criticalPages: ['/products', '/news', '/contact', '/support', '/about'],
|
|||
|
|
// 预加载延迟 (毫秒)
|
|||
|
|
prefetchDelay: 100,
|
|||
|
|
// 关键图片列表
|
|||
|
|
criticalImages: [
|
|||
|
|
'/images/hero-bg.jpg',
|
|||
|
|
'/images/cloud-services.jpg',
|
|||
|
|
'/images/aws-logo.png',
|
|||
|
|
'/images/server-room.jpg',
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 图片优化配置
|
|||
|
|
images: {
|
|||
|
|
// 图片质量
|
|||
|
|
quality: 85,
|
|||
|
|
// 响应式图片断点
|
|||
|
|
breakpoints: [640, 768, 1024, 1280, 1536],
|
|||
|
|
// 图片格式优先级
|
|||
|
|
formats: ['webp', 'avif', 'jpg', 'png'],
|
|||
|
|
// 懒加载距离
|
|||
|
|
lazyLoadRootMargin: '50px',
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 代码分割配置
|
|||
|
|
codeSplitting: {
|
|||
|
|
// 重要组件预加载
|
|||
|
|
importantComponents: [
|
|||
|
|
'Header',
|
|||
|
|
'Footer',
|
|||
|
|
'NewsArticleServerComponent',
|
|||
|
|
'ProductsServerComponent',
|
|||
|
|
],
|
|||
|
|
// 延迟加载的组件
|
|||
|
|
lazyComponents: [
|
|||
|
|
'ContactForm',
|
|||
|
|
'NewsletterSubscribe',
|
|||
|
|
'ChatWidget',
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 性能监控配置
|
|||
|
|
monitoring: {
|
|||
|
|
// Core Web Vitals 阈值
|
|||
|
|
coreWebVitals: {
|
|||
|
|
lcp: 2500, // Largest Contentful Paint (ms)
|
|||
|
|
fid: 100, // First Input Delay (ms)
|
|||
|
|
cls: 0.1, // Cumulative Layout Shift
|
|||
|
|
},
|
|||
|
|
// 启用性能监控
|
|||
|
|
enabled: process.env.NODE_ENV === 'production',
|
|||
|
|
// 性能数据上报URL
|
|||
|
|
reportUrl: process.env.NEXT_PUBLIC_PERFORMANCE_API,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// CDN 配置
|
|||
|
|
cdn: {
|
|||
|
|
// 静态资源CDN域名
|
|||
|
|
domain: process.env.NEXT_PUBLIC_CDN_DOMAIN || '',
|
|||
|
|
// 图片CDN域名
|
|||
|
|
imageDomain: process.env.NEXT_PUBLIC_IMAGE_CDN_DOMAIN || '',
|
|||
|
|
// 启用CDN
|
|||
|
|
enabled: process.env.NODE_ENV === 'production',
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 压缩配置
|
|||
|
|
compression: {
|
|||
|
|
// 启用gzip压缩
|
|||
|
|
gzip: true,
|
|||
|
|
// 启用brotli压缩
|
|||
|
|
brotli: true,
|
|||
|
|
// 压缩级别
|
|||
|
|
level: 6,
|
|||
|
|
// 最小压缩文件大小
|
|||
|
|
threshold: 1024,
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 性能优化工具函数
|
|||
|
|
export class PerformanceOptimizer {
|
|||
|
|
// 预加载关键资源
|
|||
|
|
static preloadCriticalResources() {
|
|||
|
|
if (typeof window === 'undefined') return;
|
|||
|
|
|
|||
|
|
// 预加载关键图片
|
|||
|
|
performanceConfig.prefetch.criticalImages.forEach(src => {
|
|||
|
|
// 检查是否已经存在相同的预加载链接
|
|||
|
|
if (!document.querySelector(`link[rel="preload"][as="image"][href="${src}"]`)) {
|
|||
|
|
const link = document.createElement('link');
|
|||
|
|
link.rel = 'preload';
|
|||
|
|
link.as = 'image';
|
|||
|
|
link.href = src;
|
|||
|
|
document.head.appendChild(link);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 预加载关键字体
|
|||
|
|
const criticalFonts = [
|
|||
|
|
'/fonts/inter-var.woff2',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
criticalFonts.forEach(href => {
|
|||
|
|
// 检查是否已经存在相同的预加载链接
|
|||
|
|
if (!document.querySelector(`link[rel="preload"][as="font"][href="${href}"]`)) {
|
|||
|
|
const link = document.createElement('link');
|
|||
|
|
link.rel = 'preload';
|
|||
|
|
link.as = 'font';
|
|||
|
|
link.type = 'font/woff2';
|
|||
|
|
link.crossOrigin = 'anonymous';
|
|||
|
|
link.href = href;
|
|||
|
|
document.head.appendChild(link);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监控页面性能
|
|||
|
|
static monitorPerformance() {
|
|||
|
|
if (typeof window === 'undefined' || !performanceConfig.monitoring.enabled) return;
|
|||
|
|
|
|||
|
|
// 监控Core Web Vitals (需要安装 web-vitals 包)
|
|||
|
|
try {
|
|||
|
|
// 动态导入web-vitals以避免类型错误
|
|||
|
|
const loadWebVitals = async () => {
|
|||
|
|
try {
|
|||
|
|
const webVitals = await import('web-vitals');
|
|||
|
|
webVitals.onCLS(this.sendToAnalytics);
|
|||
|
|
// onFID已被onINP替代
|
|||
|
|
if ('onINP' in webVitals) {
|
|||
|
|
(webVitals as any).onINP(this.sendToAnalytics);
|
|||
|
|
} else if ('onFID' in webVitals) {
|
|||
|
|
(webVitals as any).onFID(this.sendToAnalytics);
|
|||
|
|
}
|
|||
|
|
webVitals.onLCP(this.sendToAnalytics);
|
|||
|
|
} catch (err) {
|
|||
|
|
console.log('web-vitals not available');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
loadWebVitals();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.log('web-vitals not available');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监控导航时间
|
|||
|
|
window.addEventListener('load', () => {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const navigationTiming = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
|
|||
|
|
if (navigationTiming) {
|
|||
|
|
const metrics = {
|
|||
|
|
dns: navigationTiming.domainLookupEnd - navigationTiming.domainLookupStart,
|
|||
|
|
tcp: navigationTiming.connectEnd - navigationTiming.connectStart,
|
|||
|
|
request: navigationTiming.responseStart - navigationTiming.requestStart,
|
|||
|
|
response: navigationTiming.responseEnd - navigationTiming.responseStart,
|
|||
|
|
dom: navigationTiming.domContentLoadedEventEnd - navigationTiming.domContentLoadedEventStart,
|
|||
|
|
load: navigationTiming.loadEventEnd - navigationTiming.loadEventStart,
|
|||
|
|
total: navigationTiming.loadEventEnd - navigationTiming.fetchStart,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
console.log('页面性能指标:', metrics);
|
|||
|
|
this.sendPerformanceData(metrics);
|
|||
|
|
}
|
|||
|
|
}, 0);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发送性能数据到分析服务
|
|||
|
|
static sendToAnalytics(metric: any) {
|
|||
|
|
if (performanceConfig.monitoring.reportUrl) {
|
|||
|
|
fetch(performanceConfig.monitoring.reportUrl, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify(metric),
|
|||
|
|
}).catch(err => console.warn('性能数据上报失败:', err));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发送性能数据
|
|||
|
|
static sendPerformanceData(data: any) {
|
|||
|
|
if (performanceConfig.monitoring.reportUrl) {
|
|||
|
|
fetch(performanceConfig.monitoring.reportUrl, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({ type: 'navigation', data }),
|
|||
|
|
}).catch(err => console.warn('性能数据上报失败:', err));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 优化图片加载
|
|||
|
|
static optimizeImageLoading() {
|
|||
|
|
if (typeof window === 'undefined') return;
|
|||
|
|
|
|||
|
|
// 创建Intersection Observer监听图片懒加载
|
|||
|
|
const imageObserver = new IntersectionObserver(
|
|||
|
|
(entries) => {
|
|||
|
|
entries.forEach(entry => {
|
|||
|
|
if (entry.isIntersecting) {
|
|||
|
|
const img = entry.target as HTMLImageElement;
|
|||
|
|
if (img.dataset.src) {
|
|||
|
|
img.src = img.dataset.src;
|
|||
|
|
img.removeAttribute('data-src');
|
|||
|
|
imageObserver.unobserve(img);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
rootMargin: performanceConfig.images.lazyLoadRootMargin,
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 观察所有延迟加载的图片
|
|||
|
|
document.querySelectorAll('img[data-src]').forEach(img => {
|
|||
|
|
imageObserver.observe(img);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 在页面卸载时断开Observer
|
|||
|
|
const cleanup = () => {
|
|||
|
|
imageObserver.disconnect();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 监听页面卸载事件
|
|||
|
|
window.addEventListener('beforeunload', cleanup);
|
|||
|
|
|
|||
|
|
// 返回清理函数以便手动调用
|
|||
|
|
return cleanup;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 预热缓存
|
|||
|
|
static warmupCache(urls: string[]) {
|
|||
|
|
if (typeof window === 'undefined') return;
|
|||
|
|
|
|||
|
|
urls.forEach(url => {
|
|||
|
|
// 使用fetch预热缓存,但不等待响应
|
|||
|
|
fetch(url, {
|
|||
|
|
method: 'HEAD',
|
|||
|
|
mode: 'no-cors'
|
|||
|
|
}).catch(() => {
|
|||
|
|
// 忽略错误,这只是预热缓存
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 延迟加载非关键资源
|
|||
|
|
static deferNonCriticalResources() {
|
|||
|
|
if (typeof window === 'undefined') return;
|
|||
|
|
|
|||
|
|
// 延迟加载第三方脚本
|
|||
|
|
setTimeout(() => {
|
|||
|
|
// 可以在这里加载分析脚本、客服组件等
|
|||
|
|
interface ScriptConfig {
|
|||
|
|
src: string;
|
|||
|
|
async?: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const scripts: ScriptConfig[] = [
|
|||
|
|
// 示例:{ src: 'https://example.com/analytics.js', async: true },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
scripts.forEach(script => {
|
|||
|
|
// 检查脚本是否已经存在
|
|||
|
|
if (!document.querySelector(`script[src="${script.src}"]`)) {
|
|||
|
|
const scriptElement = document.createElement('script');
|
|||
|
|
scriptElement.src = script.src;
|
|||
|
|
scriptElement.async = script.async || true;
|
|||
|
|
document.head.appendChild(scriptElement);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}, 3000); // 3秒后加载
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页面加载优化Hook
|
|||
|
|
let optimizationInitialized = false;
|
|||
|
|
let imageLoadingCleanup: (() => void) | null = null;
|
|||
|
|
|
|||
|
|
export function usePageLoadOptimization() {
|
|||
|
|
if (typeof window !== 'undefined' && !optimizationInitialized) {
|
|||
|
|
optimizationInitialized = true;
|
|||
|
|
|
|||
|
|
// 预加载关键资源
|
|||
|
|
setTimeout(() => {
|
|||
|
|
PerformanceOptimizer.preloadCriticalResources();
|
|||
|
|
}, performanceConfig.prefetch.prefetchDelay);
|
|||
|
|
|
|||
|
|
// 监控性能
|
|||
|
|
PerformanceOptimizer.monitorPerformance();
|
|||
|
|
|
|||
|
|
// 优化图片加载
|
|||
|
|
imageLoadingCleanup = PerformanceOptimizer.optimizeImageLoading() || null;
|
|||
|
|
|
|||
|
|
// 延迟加载非关键资源
|
|||
|
|
PerformanceOptimizer.deferNonCriticalResources();
|
|||
|
|
|
|||
|
|
// 页面卸载和路由变化时清理
|
|||
|
|
const cleanup = () => {
|
|||
|
|
optimizationInitialized = false;
|
|||
|
|
if (imageLoadingCleanup) {
|
|||
|
|
imageLoadingCleanup();
|
|||
|
|
imageLoadingCleanup = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理预加载的脚本和链接
|
|||
|
|
try {
|
|||
|
|
const preloadedScripts = document.querySelectorAll('script[data-perf-preload]');
|
|||
|
|
const preloadedLinks = document.querySelectorAll('link[data-perf-preload]');
|
|||
|
|
|
|||
|
|
preloadedScripts.forEach(script => {
|
|||
|
|
if (script.parentNode) {
|
|||
|
|
script.parentNode.removeChild(script);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
preloadedLinks.forEach(link => {
|
|||
|
|
if (link.parentNode) {
|
|||
|
|
link.parentNode.removeChild(link);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('清理预加载资源时出错:', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 监听各种页面卸载和路由变化事件
|
|||
|
|
window.addEventListener('beforeunload', cleanup);
|
|||
|
|
|
|||
|
|
// 监听 Next.js 路由变化 (用于 SPA 导航)
|
|||
|
|
if (typeof window !== 'undefined' && window.history) {
|
|||
|
|
const originalPushState = window.history.pushState;
|
|||
|
|
const originalReplaceState = window.history.replaceState;
|
|||
|
|
|
|||
|
|
window.history.pushState = function(...args) {
|
|||
|
|
cleanup();
|
|||
|
|
return originalPushState.apply(this, args);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.history.replaceState = function(...args) {
|
|||
|
|
cleanup();
|
|||
|
|
return originalReplaceState.apply(this, args);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.addEventListener('popstate', cleanup);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return cleanup;
|
|||
|
|
}
|
|||
|
|
}
|