diff --git a/website/assets/js/web-vitals.js b/website/assets/js/web-vitals.js index e32a4625..56c23563 100644 --- a/website/assets/js/web-vitals.js +++ b/website/assets/js/web-vitals.js @@ -5,176 +5,24 @@ 'use strict'; function sendToAnalytics(metric) { - const safeEntries = Array.isArray(metric.entries) ? metric.entries : []; const safeValue = Number.isFinite(metric.value) ? Math.round(metric.value) : 0; - const safeDelta = Number.isFinite(metric.delta) ? Math.round(metric.delta) : 0; - const data = { - name: metric.name, - value: safeValue, - id: metric.id || 'unknown', - delta: safeDelta, - entries: safeEntries.map(e => ({ - name: e.name, - startTime: e.startTime, - duration: e.duration - })), - url: window.location.href, - timestamp: new Date().toISOString() - }; - - const payload = JSON.stringify(data); - - // Send to analytics endpoint - if (navigator.sendBeacon) { - const blob = new Blob([payload], { type: 'application/json' }); - const ok = navigator.sendBeacon('/api/web-vitals', blob); - if (!ok) { - fetch('/api/web-vitals', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: payload, - keepalive: true - }).catch(() => {}); - } - } else { - fetch('/api/web-vitals', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: payload, - keepalive: true - }).catch(() => {}); + // In production we log to console. The /api/web-vitals endpoint was removed + // to reduce unnecessary network noise as we are not running a telemetry backend. + console.log(`[Web Vitals] ${metric.name}: ${safeValue}`); + } + + // Load web-vitals from CDN + const script = document.createElement('script'); + script.src = 'https://unpkg.com/web-vitals@3/dist/web-vitals.iife.js'\; + script.onload = function() { + if (window.webVitals) { + window.webVitals.onCLS(sendToAnalytics); + window.webVitals.onFID(sendToAnalytics); + window.webVitals.onLCP(sendToAnalytics); + window.webVitals.onFCP(sendToAnalytics); + window.webVitals.onTTFB(sendToAnalytics); } - - // Also log to console in development - console.log(`[Web Vitals] ${metric.name}: ${metric.value}`, metric); - } - - // Largest Contentful Paint (LCP) - function observeLCP() { - try { - const observer = new PerformanceObserver((list) => { - const entries = list.getEntries(); - const lastEntry = entries[entries.length - 1]; - sendToAnalytics({ - name: 'LCP', - value: lastEntry.renderTime || lastEntry.loadTime, - id: lastEntry.id, - delta: 0, - entries: entries - }); - }); - observer.observe({ entryTypes: ['largest-contentful-paint'] }); - } catch (e) {} - } - - // First Input Delay (FID) - function observeFID() { - try { - const observer = new PerformanceObserver((list) => { - const entries = list.getEntries(); - entries.forEach(entry => { - sendToAnalytics({ - name: 'FID', - value: entry.processingStart - entry.startTime, - id: entry.id, - delta: 0, - entries: [entry] - }); - }); - }); - observer.observe({ entryTypes: ['first-input'] }); - } catch (e) {} - } - - // Cumulative Layout Shift (CLS) - function observeCLS() { - try { - let clsValue = 0; - const observer = new PerformanceObserver((list) => { - try { - const entries = list.getEntries(); - entries.forEach(entry => { - if (!entry.hadRecentInput) { - clsValue += entry.value; - } - }); - sendToAnalytics({ - name: 'CLS', - value: clsValue, - id: 'cls', - delta: 0, - entries: entries - }); - } catch (e) { - // CLS measurement failed, but don't crash the whole script - console.log('CLS measurement error:', e.message); - } - }); - - // Try to observe layout-shift, but handle if it's not supported - try { - observer.observe({ entryTypes: ['layout-shift'] }); - } catch (e) { - console.log('Layout-shift observation not supported:', e.message); - // CLS will not be measured, but other metrics will still work - } - } catch (e) { - console.log('CLS observer setup failed:', e.message); - } - } - - // Time to First Byte (TTFB) - function measureTTFB() { - try { - const nav = performance.getEntriesByType('navigation')[0]; - if (nav) { - sendToAnalytics({ - name: 'TTFB', - value: nav.responseStart, - id: 'ttfb', - delta: 0, - entries: [nav] - }); - } - } catch (e) {} - } - - // First Contentful Paint (FCP) - function observeFCP() { - try { - const observer = new PerformanceObserver((list) => { - const entries = list.getEntries(); - entries.forEach(entry => { - if (entry.name === 'first-contentful-paint') { - sendToAnalytics({ - name: 'FCP', - value: entry.startTime, - id: entry.id, - delta: 0, - entries: [entry] - }); - } - }); - }); - observer.observe({ entryTypes: ['paint'] }); - } catch (e) {} - } - - // Initialize when page loads - if (document.readyState === 'complete') { - observeLCP(); - observeFID(); - observeCLS(); - observeFCP(); - measureTTFB(); - } else { - window.addEventListener('load', () => { - observeLCP(); - observeFID(); - observeCLS(); - observeFCP(); - measureTTFB(); - }); - } + }; + document.head.appendChild(script); })();