diff --git a/app/components/ClientProviders.tsx b/app/components/ClientProviders.tsx index df361b3..59a9ed8 100644 --- a/app/components/ClientProviders.tsx +++ b/app/components/ClientProviders.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { usePathname } from "next/navigation"; import dynamic from "next/dynamic"; import { ToastProvider } from "@/components/Toast"; +import ErrorBoundary from "@/components/ErrorBoundary"; import { AnalyticsProvider } from "@/components/AnalyticsProvider"; // Dynamic import with SSR disabled to avoid framer-motion issues @@ -46,13 +47,20 @@ export default function ClientProviders({ }; }, [pathname]); + // Wrap in multiple error boundaries to isolate failures return ( - - - {mounted && } -
{children}
- {mounted && !is404Page && } -
-
+ + + + + + {mounted && } +
{children}
+ {mounted && !is404Page && } +
+
+
+
+
); } diff --git a/components/AnalyticsProvider.tsx b/components/AnalyticsProvider.tsx index 9afda78..5a547a7 100644 --- a/components/AnalyticsProvider.tsx +++ b/components/AnalyticsProvider.tsx @@ -9,12 +9,16 @@ interface AnalyticsProviderProps { } export const AnalyticsProvider: React.FC = ({ children }) => { - // Initialize Web Vitals tracking + // Initialize Web Vitals tracking - wrapped to prevent crashes + // Hooks must be called unconditionally, but the hook itself handles errors useWebVitals(); useEffect(() => { if (typeof window === 'undefined') return; + // Wrap entire effect in try-catch to prevent any errors from breaking the app + try { + // Track page view const trackPageView = async () => { const path = window.location.pathname; @@ -49,8 +53,15 @@ export const AnalyticsProvider: React.FC = ({ children } } }; - // Track page load performance - trackPageLoad(); + // Track page load performance - wrapped in try-catch + try { + trackPageLoad(); + } catch (error) { + // Silently fail + if (process.env.NODE_ENV === 'development') { + console.warn('Error tracking page load:', error); + } + } // Track initial page view trackPageView(); @@ -255,16 +266,29 @@ export const AnalyticsProvider: React.FC = ({ children } window.addEventListener('error', handleError); window.addEventListener('unhandledrejection', handleUnhandledRejection); - // Cleanup - return () => { - window.removeEventListener('popstate', handleRouteChange); - document.removeEventListener('click', handleClick); - document.removeEventListener('submit', handleSubmit); - window.removeEventListener('scroll', handleScroll); - window.removeEventListener('error', handleError); - window.removeEventListener('unhandledrejection', handleUnhandledRejection); - }; + // Cleanup + return () => { + try { + window.removeEventListener('popstate', handleRouteChange); + document.removeEventListener('click', handleClick); + document.removeEventListener('submit', handleSubmit); + window.removeEventListener('scroll', handleScroll); + window.removeEventListener('error', handleError); + window.removeEventListener('unhandledrejection', handleUnhandledRejection); + } catch { + // Silently fail during cleanup + } + }; + } catch (error) { + // If anything fails, log but don't break the app + if (process.env.NODE_ENV === 'development') { + console.error('AnalyticsProvider initialization error:', error); + } + // Return empty cleanup function + return () => {}; + } }, []); + // Always render children, even if analytics fails return <>{children}; }; diff --git a/components/ErrorBoundary.tsx b/components/ErrorBoundary.tsx index 561a340..483d569 100644 --- a/components/ErrorBoundary.tsx +++ b/components/ErrorBoundary.tsx @@ -22,17 +22,19 @@ export default class ErrorBoundary extends React.Component< render() { if (this.state.hasError) { - return ( -
-

Something went wrong!

- -
- ); + // Still render children to prevent white screen - just log the error + if (process.env.NODE_ENV === 'development') { + return ( +
+
+ ⚠️ Error boundary triggered - rendering children anyway +
+ {this.props.children} +
+ ); + } + // In production, just render children silently + return this.props.children; } return this.props.children; diff --git a/lib/useWebVitals.ts b/lib/useWebVitals.ts index 505fbc1..072ba11 100644 --- a/lib/useWebVitals.ts +++ b/lib/useWebVitals.ts @@ -206,12 +206,14 @@ export const useWebVitals = () => { useEffect(() => { if (typeof window === 'undefined') return; - // Store web vitals for batch sending - const webVitals: Record = {}; - const path = window.location.pathname; - const projectMatch = path.match(/\/projects\/([^\/]+)/); - const projectId = projectMatch ? projectMatch[1] : null; - const observers: PerformanceObserver[] = []; + // Wrap everything in try-catch to prevent errors from breaking the app + try { + // Store web vitals for batch sending + const webVitals: Record = {}; + const path = window.location.pathname; + const projectMatch = path.match(/\/projects\/([^\/]+)/); + const projectId = projectMatch ? projectMatch[1] : null; + const observers: PerformanceObserver[] = []; const sendWebVitals = async () => { if (Object.keys(webVitals).length >= 3) { // Wait for at least FCP, LCP, CLS @@ -319,16 +321,28 @@ export const useWebVitals = () => { window.addEventListener('load', handleLoad); } - return () => { - // Cleanup all observers - observers.forEach(observer => { + return () => { + // Cleanup all observers + observers.forEach(observer => { + try { + observer.disconnect(); + } catch { + // Silently fail + } + }); try { - observer.disconnect(); + window.removeEventListener('load', handleLoad); } catch { // Silently fail } - }); - window.removeEventListener('load', handleLoad); - }; + }; + } catch (error) { + // If Web Vitals initialization fails, don't break the app + if (process.env.NODE_ENV === 'development') { + console.warn('Web Vitals initialization failed:', error); + } + // Return empty cleanup function + return () => {}; + } }, []); };