1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
// Core Web Vitals の測定
import {getLCP, getFID, getCLS, getFCP, getTTFB} from 'web-vitals';
class PerformanceMonitor {
constructor(options = {}) {
this.options = {
reportEndpoint: '/api/performance',
sampleRate: 0.1, // 10%のサンプリング
...options
};
this.metrics = {};
this.init();
}
init() {
// サンプリング判定
if (Math.random() > this.options.sampleRate) {
return;
}
// Core Web Vitals の測定
getLCP(this.handleMetric.bind(this, 'LCP'));
getFID(this.handleMetric.bind(this, 'FID'));
getCLS(this.handleMetric.bind(this, 'CLS'));
getFCP(this.handleMetric.bind(this, 'FCP'));
getTTFB(this.handleMetric.bind(this, 'TTFB'));
// ページアンロード時にレポート送信
window.addEventListener('beforeunload', () => {
this.sendReport();
});
}
handleMetric(name, metric) {
this.metrics[name] = metric;
// リアルタイム分析
this.analyzeMetric(name, metric);
}
analyzeMetric(name, metric) {
const thresholds = {
LCP: { good: 2500, poor: 4000 },
FID: { good: 100, poor: 300 },
CLS: { good: 0.1, poor: 0.25 },
FCP: { good: 1800, poor: 3000 },
TTFB: { good: 800, poor: 1800 }
};
const threshold = thresholds[name];
let rating = 'good';
if (metric.value > threshold.poor) {
rating = 'poor';
} else if (metric.value > threshold.good) {
rating = 'needs-improvement';
}
console.log(`${name}: ${metric.value} (${rating})`);
// パフォーマンス問題の警告
if (rating === 'poor') {
this.reportPerformanceIssue(name, metric);
}
}
reportPerformanceIssue(name, metric) {
// 開発者向け警告
console.warn(`Performance issue detected: ${name} = ${metric.value}`);
// 改善提案
const suggestions = {
LCP: 'Consider optimizing images, reducing server response time, or implementing Critical CSS',
FID: 'Consider reducing JavaScript execution time or implementing code splitting',
CLS: 'Ensure images and ads have dimensions set, and avoid inserting content above existing content',
FCP: 'Consider reducing render-blocking resources or implementing Critical CSS',
TTFB: 'Consider optimizing server response time or using a CDN'
};
console.info(`Suggestion for ${name}: ${suggestions[name]}`);
}
async sendReport() {
if (Object.keys(this.metrics).length === 0) {
return;
}
const reportData = {
url: window.location.href,
userAgent: navigator.userAgent,
connectionType: navigator.connection?.effectiveType || 'unknown',
metrics: this.metrics,
timestamp: Date.now()
};
try {
// Beacon API で確実に送信
if (navigator.sendBeacon) {
navigator.sendBeacon(
this.options.reportEndpoint,
JSON.stringify(reportData)
);
} else {
// フォールバック
await fetch(this.options.reportEndpoint, {
method: 'POST',
body: JSON.stringify(reportData),
headers: {
'Content-Type': 'application/json'
},
keepalive: true
});
}
} catch (error) {
console.error('Failed to send performance report:', error);
}
}
}
// 初期化
document.addEventListener('DOMContentLoaded', () => {
new PerformanceMonitor({
sampleRate: 0.1,
reportEndpoint: '/api/performance'
});
});
|