{
+ if (isProduction) return fn();
+
+ this.startTimer(name);
+ const result = await fn();
+ this.endTimer(name);
+ return result;
+ }
+
+ /**
+ * Get all performance entries
+ */
+ public getEntries(): PerformanceEntry[] {
+ return [...this.entries];
+ }
+
+ /**
+ * Clear all entries
+ */
+ public clearEntries(): void {
+ this.entries = [];
+ }
+
+ /**
+ * Log a summary of all performance entries
+ */
+ public logSummary(): void {
+ if (isProduction || this.entries.length === 0) return;
+
+ console.group('š Performance Summary');
+
+ const sortedEntries = this.entries
+ .filter(entry => entry.duration !== undefined)
+ .sort((a, b) => (b.duration || 0) - (a.duration || 0));
+
+ console.table(
+ sortedEntries.map(entry => ({
+ Name: entry.name,
+ Duration: `${(entry.duration || 0).toFixed(2)}ms`,
+ 'Start Time': `${entry.startTime.toFixed(2)}ms`
+ }))
+ );
+
+ const totalTime = sortedEntries.reduce((sum, entry) => sum + (entry.duration || 0), 0);
+ console.log(`%cTotal measured time: ${totalTime.toFixed(2)}ms`, 'font-weight: bold;');
+
+ console.groupEnd();
+ }
+}
+
+// Create default instance
+export const performanceMonitor = new PerformanceMonitor();
+
+/**
+ * Higher-order component to measure component render performance
+ */
+export function withPerformanceMonitoring(
+ Component: React.ComponentType
,
+ componentName?: string
+): React.ComponentType
{
+ if (isProduction) return Component;
+
+ const name = componentName || Component.displayName || Component.name || 'Unknown';
+
+ const WrappedComponent = (props: P) => {
+ React.useEffect(() => {
+ performanceMonitor.startTimer(`${name} mount`);
+ return () => {
+ performanceMonitor.endTimer(`${name} mount`);
+ };
+ }, []);
+
+ React.useEffect(() => {
+ performanceMonitor.endTimer(`${name} render`);
+ });
+
+ performanceMonitor.startTimer(`${name} render`);
+ return React.createElement(Component, props);
+ };
+
+ WrappedComponent.displayName = `withPerformanceMonitoring(${name})`;
+ return WrappedComponent;
+}
+
+/**
+ * Hook to measure render performance
+ */
+export function useRenderPerformance(componentName: string): void {
+ if (isProduction) return;
+
+ const renderCount = React.useRef(0);
+ const startTime = React.useRef(0);
+
+ React.useEffect(() => {
+ renderCount.current += 1;
+ const endTime = performance.now();
+ const duration = endTime - startTime.current;
+
+ if (renderCount.current > 1) {
+ console.log(
+ `%cš ${componentName} render #${renderCount.current}: ${duration.toFixed(2)}ms`,
+ 'color: #1890ff; font-size: 11px;'
+ );
+ }
+ });
+
+ startTime.current = performance.now();
+}
+
+export default performanceMonitor;
\ No newline at end of file
diff --git a/worklenz-frontend/src/utils/routePreloader.ts b/worklenz-frontend/src/utils/routePreloader.ts
new file mode 100644
index 00000000..8df8a3b4
--- /dev/null
+++ b/worklenz-frontend/src/utils/routePreloader.ts
@@ -0,0 +1,181 @@
+import React from 'react';
+
+/**
+ * Route preloader utility to prefetch components and improve navigation performance
+ */
+
+interface PreloadableRoute {
+ path: string;
+ loader: () => Promise;
+ priority: 'high' | 'medium' | 'low';
+}
+
+class RoutePreloader {
+ private preloadedRoutes = new Set();
+ private preloadQueue: PreloadableRoute[] = [];
+ private isPreloading = false;
+
+ /**
+ * Register a route for preloading
+ */
+ public registerRoute(path: string, loader: () => Promise, priority: 'high' | 'medium' | 'low' = 'medium'): void {
+ if (this.preloadedRoutes.has(path)) return;
+
+ this.preloadQueue.push({ path, loader, priority });
+ this.sortQueue();
+ }
+
+ /**
+ * Preload a specific route immediately
+ */
+ public async preloadRoute(path: string, loader: () => Promise): Promise {
+ if (this.preloadedRoutes.has(path)) return;
+
+ try {
+ await loader();
+ this.preloadedRoutes.add(path);
+ } catch (error) {
+ console.warn(`Failed to preload route: ${path}`, error);
+ }
+ }
+
+ /**
+ * Start preloading routes in the queue
+ */
+ public async startPreloading(): Promise {
+ if (this.isPreloading || this.preloadQueue.length === 0) return;
+
+ this.isPreloading = true;
+
+ // Use requestIdleCallback if available, otherwise setTimeout
+ const scheduleWork = (callback: () => void) => {
+ if ('requestIdleCallback' in window) {
+ requestIdleCallback(callback, { timeout: 1000 });
+ } else {
+ setTimeout(callback, 0);
+ }
+ };
+
+ const processQueue = async () => {
+ while (this.preloadQueue.length > 0) {
+ const route = this.preloadQueue.shift();
+ if (!route) break;
+
+ if (this.preloadedRoutes.has(route.path)) continue;
+
+ try {
+ await route.loader();
+ this.preloadedRoutes.add(route.path);
+ } catch (error) {
+ console.warn(`Failed to preload route: ${route.path}`, error);
+ }
+
+ // Yield control back to the browser
+ await new Promise(resolve => scheduleWork(() => resolve()));
+ }
+
+ this.isPreloading = false;
+ };
+
+ scheduleWork(processQueue);
+ }
+
+ /**
+ * Preload routes on user interaction (hover, focus)
+ */
+ public preloadOnInteraction(element: HTMLElement, path: string, loader: () => Promise): void {
+ if (this.preloadedRoutes.has(path)) return;
+
+ let preloadTriggered = false;
+
+ const handleInteraction = () => {
+ if (preloadTriggered) return;
+ preloadTriggered = true;
+
+ this.preloadRoute(path, loader);
+
+ // Clean up listeners
+ element.removeEventListener('mouseenter', handleInteraction);
+ element.removeEventListener('focus', handleInteraction);
+ element.removeEventListener('touchstart', handleInteraction);
+ };
+
+ element.addEventListener('mouseenter', handleInteraction, { passive: true });
+ element.addEventListener('focus', handleInteraction, { passive: true });
+ element.addEventListener('touchstart', handleInteraction, { passive: true });
+ }
+
+ /**
+ * Preload routes when the browser is idle
+ */
+ public preloadOnIdle(): void {
+ if ('requestIdleCallback' in window) {
+ requestIdleCallback(() => {
+ this.startPreloading();
+ }, { timeout: 2000 });
+ } else {
+ setTimeout(() => {
+ this.startPreloading();
+ }, 1000);
+ }
+ }
+
+ /**
+ * Check if a route is already preloaded
+ */
+ public isRoutePreloaded(path: string): boolean {
+ return this.preloadedRoutes.has(path);
+ }
+
+ /**
+ * Clear all preloaded routes
+ */
+ public clearPreloaded(): void {
+ this.preloadedRoutes.clear();
+ this.preloadQueue = [];
+ }
+
+ private sortQueue(): void {
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
+ this.preloadQueue.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
+ }
+}
+
+// Create default instance
+export const routePreloader = new RoutePreloader();
+
+/**
+ * React hook to preload routes on component mount
+ */
+export function useRoutePreloader(routes: Array<{ path: string; loader: () => Promise; priority?: 'high' | 'medium' | 'low' }>): void {
+ React.useEffect(() => {
+ routes.forEach(route => {
+ routePreloader.registerRoute(route.path, route.loader, route.priority);
+ });
+
+ // Start preloading after a short delay to not interfere with initial render
+ const timer = setTimeout(() => {
+ routePreloader.preloadOnIdle();
+ }, 100);
+
+ return () => clearTimeout(timer);
+ }, [routes]);
+}
+
+/**
+ * React hook to preload a route on element interaction
+ */
+export function usePreloadOnHover(path: string, loader: () => Promise) {
+ const elementRef = React.useRef(null);
+
+ React.useEffect(() => {
+ const element = elementRef.current;
+ if (!element) return;
+
+ routePreloader.preloadOnInteraction(element, path, loader);
+ }, [path, loader]);
+
+ return elementRef;
+}
+
+export default routePreloader;
\ No newline at end of file