- Add auto-deploy.sh script with full CI/CD pipeline - Add quick-deploy.sh for fast development deployments - Add Git post-receive hook for automatic deployment on push - Add comprehensive deployment documentation - Add npm scripts for easy deployment management - Include health checks, logging, and cleanup - Support for automatic rollback on failures
186 lines
4.9 KiB
TypeScript
186 lines
4.9 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
import { trackWebVitals, trackPerformance } from './analytics';
|
|
|
|
// Web Vitals types
|
|
interface Metric {
|
|
name: string;
|
|
value: number;
|
|
delta: number;
|
|
id: string;
|
|
}
|
|
|
|
// Simple Web Vitals implementation (since we don't want to add external dependencies)
|
|
const getCLS = (onPerfEntry: (metric: Metric) => void) => {
|
|
let clsValue = 0;
|
|
let sessionValue = 0;
|
|
let sessionEntries: PerformanceEntry[] = [];
|
|
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (!(entry as any).hadRecentInput) {
|
|
const firstSessionEntry = sessionEntries[0];
|
|
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
|
|
|
|
if (sessionValue && entry.startTime - lastSessionEntry.startTime < 1000 && entry.startTime - firstSessionEntry.startTime < 5000) {
|
|
sessionValue += (entry as any).value;
|
|
sessionEntries.push(entry);
|
|
} else {
|
|
sessionValue = (entry as any).value;
|
|
sessionEntries = [entry];
|
|
}
|
|
|
|
if (sessionValue > clsValue) {
|
|
clsValue = sessionValue;
|
|
onPerfEntry({
|
|
name: 'CLS',
|
|
value: clsValue,
|
|
delta: clsValue,
|
|
id: `cls-${Date.now()}`,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'layout-shift', buffered: true });
|
|
};
|
|
|
|
const getFID = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
onPerfEntry({
|
|
name: 'FID',
|
|
value: (entry as any).processingStart - entry.startTime,
|
|
delta: (entry as any).processingStart - entry.startTime,
|
|
id: `fid-${Date.now()}`,
|
|
});
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'first-input', buffered: true });
|
|
};
|
|
|
|
const getFCP = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (entry.name === 'first-contentful-paint') {
|
|
onPerfEntry({
|
|
name: 'FCP',
|
|
value: entry.startTime,
|
|
delta: entry.startTime,
|
|
id: `fcp-${Date.now()}`,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'paint', buffered: true });
|
|
};
|
|
|
|
const getLCP = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
const lastEntry = entries[entries.length - 1];
|
|
|
|
onPerfEntry({
|
|
name: 'LCP',
|
|
value: lastEntry.startTime,
|
|
delta: lastEntry.startTime,
|
|
id: `lcp-${Date.now()}`,
|
|
});
|
|
});
|
|
|
|
observer.observe({ type: 'largest-contentful-paint', buffered: true });
|
|
};
|
|
|
|
const getTTFB = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (entry.entryType === 'navigation') {
|
|
const navEntry = entry as PerformanceNavigationTiming;
|
|
onPerfEntry({
|
|
name: 'TTFB',
|
|
value: navEntry.responseStart - navEntry.fetchStart,
|
|
delta: navEntry.responseStart - navEntry.fetchStart,
|
|
id: `ttfb-${Date.now()}`,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'navigation', buffered: true });
|
|
};
|
|
|
|
// Custom hook for Web Vitals tracking
|
|
export const useWebVitals = () => {
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
// Track Core Web Vitals
|
|
getCLS((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getFID((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getFCP((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getLCP((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getTTFB((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
// Track page load performance
|
|
const handleLoad = () => {
|
|
setTimeout(() => {
|
|
trackPerformance({
|
|
name: 'page-load-complete',
|
|
value: performance.now(),
|
|
url: window.location.pathname,
|
|
timestamp: Date.now(),
|
|
userAgent: navigator.userAgent,
|
|
});
|
|
}, 0);
|
|
};
|
|
|
|
if (document.readyState === 'complete') {
|
|
handleLoad();
|
|
} else {
|
|
window.addEventListener('load', handleLoad);
|
|
}
|
|
|
|
return () => {
|
|
window.removeEventListener('load', handleLoad);
|
|
};
|
|
}, []);
|
|
};
|