@@ -9,6 +9,133 @@ export class FrameworkWriter {
99 this . assetMappings = new Map ( ) ; // absolute URL (and alternates) -> local ./assets/... path
1010 }
1111
12+ /**
13+ * Adds error handling for Next.js/React applications in offline mode
14+ * @param {Object } $ - Cheerio DOM instance
15+ */
16+ addOfflineErrorHandling ( $ ) {
17+ // Detect if this is a Next.js application
18+ const isNextJs = this . cloner . analysis ?. primaryFramework ?. key === 'nextjs' ||
19+ $ ( '#__next' ) . length > 0 ||
20+ $ ( 'script[src*="_next"]' ) . length > 0 ;
21+
22+ const isReact = this . cloner . analysis ?. primaryFramework ?. key === 'react' ||
23+ $ ( '#root' ) . length > 0 ||
24+ $ ( 'script[src*="react"]' ) . length > 0 ;
25+
26+ if ( isNextJs || isReact ) {
27+ // Add error boundary script for React/Next.js applications
28+ const errorBoundaryScript = `
29+ <script>
30+ (function() {
31+ // Global error handler for React/Next.js offline mode
32+ window.addEventListener('error', function(event) {
33+ console.warn('Mirror Web CLI: Handling offline error:', event.error?.message || event.message);
34+
35+ // Hide the default Next.js error overlay
36+ const errorOverlay = document.querySelector('[data-nextjs-dialog-overlay]');
37+ if (errorOverlay) {
38+ errorOverlay.style.display = 'none';
39+ }
40+
41+ // Check if the app root is empty and show a fallback
42+ const appRoot = document.querySelector('#__next, #root, [data-reactroot]');
43+ if (appRoot && (!appRoot.innerHTML || appRoot.innerHTML.trim() === '')) {
44+ appRoot.innerHTML = \`
45+ <div style="
46+ min-height: 100vh;
47+ display: flex;
48+ align-items: center;
49+ justify-content: center;
50+ flex-direction: column;
51+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
52+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
53+ color: white;
54+ text-align: center;
55+ padding: 2rem;
56+ ">
57+ <div style="
58+ background: rgba(255,255,255,0.1);
59+ backdrop-filter: blur(10px);
60+ border-radius: 20px;
61+ padding: 3rem;
62+ max-width: 600px;
63+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
64+ ">
65+ <h1 style="margin: 0 0 1rem 0; font-size: 2.5rem; font-weight: 300;">
66+ 🪞 Offline Mirror
67+ </h1>
68+ <p style="margin: 0 0 1.5rem 0; font-size: 1.2rem; opacity: 0.9;">
69+ This is an offline mirror of <strong>${ this . cloner . url } </strong>
70+ </p>
71+ <p style="margin: 0; font-size: 1rem; opacity: 0.7;">
72+ Some interactive features may not work in offline mode.<br>
73+ This static mirror preserves the visual content and structure.
74+ </p>
75+ <div style="margin-top: 2rem; font-size: 0.9rem; opacity: 0.6;">
76+ Generated by Mirror Web CLI v1.0
77+ </div>
78+ </div>
79+ </div>
80+ \`;
81+ }
82+
83+ return true; // Prevent default error handling
84+ });
85+
86+ // Handle unhandled promise rejections
87+ window.addEventListener('unhandledrejection', function(event) {
88+ console.warn('Mirror Web CLI: Handling offline promise rejection:', event.reason);
89+ event.preventDefault(); // Prevent default handling
90+ });
91+
92+ // Override fetch for offline mode
93+ if (typeof window.fetch !== 'undefined') {
94+ const originalFetch = window.fetch;
95+ window.fetch = function(...args) {
96+ return originalFetch(...args).catch(error => {
97+ console.warn('Mirror Web CLI: Network request failed in offline mode:', args[0]);
98+ // Return a fake response to prevent crashes
99+ return new Response('Offline mode - network request blocked', {
100+ status: 200,
101+ statusText: 'OK',
102+ headers: { 'Content-Type': 'text/plain' }
103+ });
104+ });
105+ };
106+ }
107+ })();
108+ </script>` ;
109+
110+ // Add the error handling script to the head
111+ $ ( 'head' ) . append ( errorBoundaryScript ) ;
112+
113+ // Add a noscript fallback
114+ const noscriptFallback = `
115+ <noscript>
116+ <div style="
117+ min-height: 100vh;
118+ display: flex;
119+ align-items: center;
120+ justify-content: center;
121+ flex-direction: column;
122+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
123+ background: #f8fafc;
124+ color: #334155;
125+ text-align: center;
126+ padding: 2rem;
127+ ">
128+ <h1>🪞 Offline Mirror</h1>
129+ <p>This is an offline mirror of <strong>${ this . cloner . url } </strong></p>
130+ <p>JavaScript is required to view this React/Next.js application.</p>
131+ <p>Please enable JavaScript in your browser settings.</p>
132+ </div>
133+ </noscript>` ;
134+
135+ $ ( 'body' ) . prepend ( noscriptFallback ) ;
136+ }
137+ }
138+
12139 async generateOfflineProject ( ) {
13140 await fs . ensureDir ( this . cloner . options . outputDir ) ;
14141
@@ -150,6 +277,9 @@ export class FrameworkWriter {
150277 $ ( 'head' ) . append (
151278 `<meta name="mirrored-date" content="${ new Date ( ) . toISOString ( ) } ">` ,
152279 ) ;
280+
281+ // Add Next.js/React error handling for offline mode
282+ this . addOfflineErrorHandling ( $ ) ;
153283
154284 // Stylesheets
155285 $ ( 'link[rel="stylesheet"], link[rel="preload"][as="style"]' ) . each (
0 commit comments