refactor: enhance error handling and performance tracking across components

- Improve localStorage access in ActivityFeed, ChatWidget, and AdminPage with try-catch blocks to handle potential errors gracefully.
- Update performance tracking in AnalyticsProvider and analytics.ts to ensure robust error handling and prevent failures from affecting user experience.
- Refactor Web Vitals tracking to include error handling for observer initialization and data collection.
- Ensure consistent handling of hydration mismatches in components like BackgroundBlobs and ChatWidget to improve rendering reliability.
This commit is contained in:
2026-01-10 16:53:06 +01:00
parent 20f0ccb85b
commit ca2ed13446
10 changed files with 573 additions and 268 deletions

View File

@@ -65,36 +65,43 @@ export const AnalyticsProvider: React.FC<AnalyticsProviderProps> = ({ children }
// Wait for page to fully load
setTimeout(async () => {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const paintEntries = performance.getEntriesByType('paint');
const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
const fcp = paintEntries.find((e: PerformanceEntry) => e.name === 'first-contentful-paint');
const lcp = lcpEntries[lcpEntries.length - 1];
const performanceData = {
loadTime: navigation ? navigation.loadEventEnd - navigation.fetchStart : 0,
fcp: fcp ? fcp.startTime : 0,
lcp: lcp ? lcp.startTime : 0,
ttfb: navigation ? navigation.responseStart - navigation.fetchStart : 0,
cls: 0, // Will be updated by CLS observer
fid: 0, // Will be updated by FID observer
si: 0 // Speed Index - would need to calculate
};
try {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;
const paintEntries = performance.getEntriesByType('paint');
const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
const fcp = paintEntries.find((e: PerformanceEntry) => e.name === 'first-contentful-paint');
const lcp = lcpEntries.length > 0 ? lcpEntries[lcpEntries.length - 1] : undefined;
const performanceData = {
loadTime: navigation && navigation.loadEventEnd && navigation.fetchStart ? navigation.loadEventEnd - navigation.fetchStart : 0,
fcp: fcp ? fcp.startTime : 0,
lcp: lcp ? lcp.startTime : 0,
ttfb: navigation && navigation.responseStart && navigation.fetchStart ? navigation.responseStart - navigation.fetchStart : 0,
cls: 0, // Will be updated by CLS observer
fid: 0, // Will be updated by FID observer
si: 0 // Speed Index - would need to calculate
};
// Send performance data
await fetch('/api/analytics/track', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'performance',
projectId: projectId,
page: path,
performance: performanceData
})
});
// Send performance data
await fetch('/api/analytics/track', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'performance',
projectId: projectId,
page: path,
performance: performanceData
})
});
} catch (error) {
// Silently fail - performance tracking is not critical
if (process.env.NODE_ENV === 'development') {
console.warn('Error collecting performance data:', error);
}
}
}, 2000); // Wait 2 seconds for page to stabilize
} catch (error) {
// Silently fail
@@ -124,48 +131,84 @@ export const AnalyticsProvider: React.FC<AnalyticsProviderProps> = ({ children }
// Track user interactions
const handleClick = (event: MouseEvent) => {
const target = event.target as HTMLElement;
const element = target.tagName.toLowerCase();
const className = target.className;
const id = target.id;
trackEvent('click', {
element,
className: (typeof className === 'string' && className) ? className.split(' ')[0] : undefined,
id: id || undefined,
url: window.location.pathname,
});
try {
if (typeof window === 'undefined') return;
const target = event.target as HTMLElement | null;
if (!target) return;
const element = target.tagName ? target.tagName.toLowerCase() : 'unknown';
const className = target.className;
const id = target.id;
trackEvent('click', {
element,
className: (typeof className === 'string' && className) ? className.split(' ')[0] : undefined,
id: id || undefined,
url: window.location.pathname,
});
} catch (error) {
// Silently fail - click tracking is not critical
if (process.env.NODE_ENV === 'development') {
console.warn('Error tracking click:', error);
}
}
};
// Track form submissions
const handleSubmit = (event: SubmitEvent) => {
const form = event.target as HTMLFormElement;
trackEvent('form-submit', {
formId: form.id || undefined,
formClass: form.className || undefined,
url: window.location.pathname,
});
try {
if (typeof window === 'undefined') return;
const form = event.target as HTMLFormElement | null;
if (!form) return;
trackEvent('form-submit', {
formId: form.id || undefined,
formClass: form.className || undefined,
url: window.location.pathname,
});
} catch (error) {
// Silently fail - form tracking is not critical
if (process.env.NODE_ENV === 'development') {
console.warn('Error tracking form submit:', error);
}
}
};
// Track scroll depth
let maxScrollDepth = 0;
const handleScroll = () => {
const scrollDepth = Math.round(
(window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
);
if (scrollDepth > maxScrollDepth) {
maxScrollDepth = scrollDepth;
try {
if (typeof window === 'undefined' || typeof document === 'undefined') return;
// Track scroll milestones
if (scrollDepth >= 25 && scrollDepth < 50 && maxScrollDepth >= 25) {
trackEvent('scroll-depth', { depth: 25, url: window.location.pathname });
} else if (scrollDepth >= 50 && scrollDepth < 75 && maxScrollDepth >= 50) {
trackEvent('scroll-depth', { depth: 50, url: window.location.pathname });
} else if (scrollDepth >= 75 && scrollDepth < 90 && maxScrollDepth >= 75) {
trackEvent('scroll-depth', { depth: 75, url: window.location.pathname });
} else if (scrollDepth >= 90 && maxScrollDepth >= 90) {
trackEvent('scroll-depth', { depth: 90, url: window.location.pathname });
const scrollHeight = document.documentElement.scrollHeight;
const innerHeight = window.innerHeight;
if (scrollHeight <= innerHeight) return; // No scrollable content
const scrollDepth = Math.round(
(window.scrollY / (scrollHeight - innerHeight)) * 100
);
if (scrollDepth > maxScrollDepth) {
maxScrollDepth = scrollDepth;
// Track scroll milestones
if (scrollDepth >= 25 && scrollDepth < 50 && maxScrollDepth >= 25) {
trackEvent('scroll-depth', { depth: 25, url: window.location.pathname });
} else if (scrollDepth >= 50 && scrollDepth < 75 && maxScrollDepth >= 50) {
trackEvent('scroll-depth', { depth: 50, url: window.location.pathname });
} else if (scrollDepth >= 75 && scrollDepth < 90 && maxScrollDepth >= 75) {
trackEvent('scroll-depth', { depth: 75, url: window.location.pathname });
} else if (scrollDepth >= 90 && maxScrollDepth >= 90) {
trackEvent('scroll-depth', { depth: 90, url: window.location.pathname });
}
}
} catch (error) {
// Silently fail - scroll tracking is not critical
if (process.env.NODE_ENV === 'development') {
console.warn('Error tracking scroll:', error);
}
}
};
@@ -177,20 +220,36 @@ export const AnalyticsProvider: React.FC<AnalyticsProviderProps> = ({ children }
// Track errors
const handleError = (event: ErrorEvent) => {
trackEvent('error', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
url: window.location.pathname,
});
try {
if (typeof window === 'undefined') return;
trackEvent('error', {
message: event.message || 'Unknown error',
filename: event.filename || undefined,
lineno: event.lineno || undefined,
colno: event.colno || undefined,
url: window.location.pathname,
});
} catch (error) {
// Silently fail - error tracking should not cause more errors
if (process.env.NODE_ENV === 'development') {
console.warn('Error tracking error event:', error);
}
}
};
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
trackEvent('unhandled-rejection', {
reason: event.reason?.toString(),
url: window.location.pathname,
});
try {
if (typeof window === 'undefined') return;
trackEvent('unhandled-rejection', {
reason: event.reason?.toString() || 'Unknown rejection',
url: window.location.pathname,
});
} catch (error) {
// Silently fail - error tracking should not cause more errors
if (process.env.NODE_ENV === 'development') {
console.warn('Error tracking unhandled rejection:', error);
}
}
};
window.addEventListener('error', handleError);