Files
dashboard/services/performanceService.ts
T

122 lines
3.2 KiB
TypeScript

interface PerformanceMetrics {
lcp: number | null;
fid: number | null;
cls: number | null;
fcp: number | null;
ttfb: number | null;
pageLoad: number | null;
}
interface ApiTiming {
name: string;
duration: number;
timestamp: number;
}
interface ComponentTiming {
name: string;
duration: number;
timestamp: number;
}
interface LayoutShift extends PerformanceEntry {
value: number;
hadRecentInput: boolean;
}
class PerformanceService {
private metrics: PerformanceMetrics = {
lcp: null,
fid: null,
cls: 0,
fcp: null,
ttfb: null,
pageLoad: null,
};
private apiTimings: ApiTiming[] = [];
private componentTimings: ComponentTiming[] = [];
constructor() {
if (typeof window !== 'undefined' && typeof PerformanceObserver !== 'undefined') {
this.observeLCP();
this.observeFID();
this.observeCLS();
this.observeFCP();
}
}
private observeFCP() {
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.name === 'first-contentful-paint') {
this.metrics.fcp = entry.startTime;
}
}
}).observe({ type: 'paint', buffered: true });
}
private observeLCP() {
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
if (entries.length > 0) {
this.metrics.lcp = entries[entries.length - 1]?.startTime ?? null;
}
}).observe({ type: 'largest-contentful-paint', buffered: true });
}
private observeFID() {
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
this.metrics.fid = (entry as PerformanceEventTiming).processingStart - entry.startTime;
}
}).observe({ type: 'first-input', buffered: true });
}
private observeCLS() {
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!(entry as LayoutShift).hadRecentInput) {
this.metrics.cls = (this.metrics.cls ?? 0) + (entry as LayoutShift).value;
}
}
}).observe({ type: 'layout-shift', buffered: true });
}
public getMetrics(): PerformanceMetrics {
if (typeof performance !== 'undefined') {
const navEntry = performance.getEntriesByType('navigation')[0] as
| PerformanceNavigationTiming
| undefined;
if (navEntry) {
this.metrics.ttfb = navEntry.responseStart - navEntry.requestStart;
this.metrics.pageLoad = navEntry.loadEventEnd - navEntry.startTime;
}
}
return this.metrics;
}
public recordApiTiming(name: string, duration: number) {
this.apiTimings.unshift({ name, duration, timestamp: Date.now() });
if (this.apiTimings.length > 10) {
this.apiTimings.pop();
}
}
public getApiTimings(): ApiTiming[] {
return this.apiTimings;
}
public recordComponentTiming(name: string, duration: number) {
this.componentTimings.unshift({ name, duration, timestamp: Date.now() });
if (this.componentTimings.length > 10) {
this.componentTimings.pop();
}
}
public getComponentTimings(): ComponentTiming[] {
return this.componentTimings;
}
}
export const performanceService = new PerformanceService();