diff --git a/src/App.tsx b/src/App.tsx index 3a1f3132..0a186dee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,11 +2,14 @@ import Lenis from 'lenis'; import { useEffect } from 'react'; import { Toaster } from 'react-hot-toast'; import { createBrowserRouter, RouterProvider } from 'react-router'; +import { useNavigationTiming } from './hooks/useNavigationTiming'; import { routes } from './routes'; const router = createBrowserRouter(routes); function App() { + useNavigationTiming(); + useEffect(() => { const lenis = new Lenis({ duration: 1.2, diff --git a/src/hooks/useNavigationTiming.ts b/src/hooks/useNavigationTiming.ts new file mode 100644 index 00000000..83520258 --- /dev/null +++ b/src/hooks/useNavigationTiming.ts @@ -0,0 +1,56 @@ +import { useEffect, useRef } from 'react'; +import { useLocation } from 'react-router'; + +interface NavigationTiming { + route: string; + dom_content_loaded_ms: number; + load_event_ms: number; + time_to_first_byte_ms: number; +} + +function readNavigationTiming(): { + timing: NavigationTiming; + startTime: number; +} | null { + const entries = performance.getEntriesByType('navigation'); + if (!entries.length) return null; + + const nav = entries[0] as PerformanceNavigationTiming; + + return { + timing: { + route: window.location.pathname, + dom_content_loaded_ms: Math.round( + nav.domContentLoadedEventEnd - nav.startTime + ), + load_event_ms: Math.round(nav.loadEventEnd - nav.startTime), + time_to_first_byte_ms: Math.round( + nav.responseStart - nav.requestStart + ), + }, + startTime: nav.startTime, + }; +} + +export function useNavigationTiming() { + const location = useLocation(); + const loggedStartTime = useRef(null); + + useEffect(() => { + function emit() { + const result = readNavigationTiming(); + if (!result) return; + if (loggedStartTime.current === result.startTime) return; + + loggedStartTime.current = result.startTime; + console.debug('[navigation-timing]', result.timing); + } + + if (document.readyState === 'complete') { + emit(); + } else { + window.addEventListener('load', emit, { once: true }); + return () => window.removeEventListener('load', emit); + } + }, [location]); +}