vkashti / pages / _document.tsx
_document.tsx
Raw
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html lang="bg">
      <Head>
        {/* Removed Google Fonts preconnects and links since we're now using next/font/google */}
        
        {/* Preload critical custom font */}
        <link 
          rel="preload" 
          href="/custom-font.woff2" 
          as="font" 
          type="font/woff2" 
          crossOrigin="anonymous"
        />
        
        {/* Optimize for back/forward cache */}
        <meta name="bfcache-opt-in" content="true" />
      </Head>
      <body>
        <Main />
        <NextScript />
        
        {/* Comprehensive bfcache fixes */}
        <script
          dangerouslySetInnerHTML={{
            __html: `
              // Fix for back/forward cache issues
              (function() {
                // 1. Handle BroadcastChannel
                if (typeof BroadcastChannel !== 'undefined') {
                  // Store the original BroadcastChannel constructor
                  const OriginalBroadcastChannel = window.BroadcastChannel;
                  
                  // Track active channels for cleanup
                  let activeChannels = [];
                  
                  // Wrap the constructor to ensure channels are closed on page hide
                  window.BroadcastChannel = function(name) {
                    // Create a deferred instance that only initializes when needed
                    let instance = null;
                    let listeners = [];
                    
                    // Create a proxy that defers initialization until the channel is actually used
                    const proxy = new Proxy({}, {
                      get: function(target, prop) {
                        if (prop === 'postMessage') {
                          // Initialize on first use
                          if (!instance) {
                            instance = new OriginalBroadcastChannel(name);
                            activeChannels.push(instance);
                          }
                          return instance.postMessage.bind(instance);
                        }
                        
                        if (prop === 'addEventListener') {
                          return function(type, listener) {
                            if (!instance) {
                              instance = new OriginalBroadcastChannel(name);
                              activeChannels.push(instance);
                            }
                            listeners.push({ type, listener });
                            instance.addEventListener(type, listener);
                          };
                        }
                        
                        if (prop === 'removeEventListener') {
                          return function(type, listener) {
                            if (instance) {
                              instance.removeEventListener(type, listener);
                              listeners = listeners.filter(l => l.type !== type || l.listener !== listener);
                              
                              // Close if no more listeners
                              if (listeners.length === 0) {
                                instance.close();
                                activeChannels = activeChannels.filter(c => c !== instance);
                                instance = null;
                              }
                            }
                          };
                        }
                        
                        if (prop === 'close') {
                          return function() {
                            if (instance) {
                              instance.close();
                              activeChannels = activeChannels.filter(c => c !== instance);
                              instance = null;
                              listeners = [];
                            }
                          };
                        }
                        
                        // For any other property, initialize if needed
                        if (!instance) {
                          instance = new OriginalBroadcastChannel(name);
                          activeChannels.push(instance);
                        }
                        return instance[prop];
                      }
                    });
                    
                    return proxy;
                  };
                  
                  // Clean up all channels on page hide
                  window.addEventListener('pagehide', function() {
                    activeChannels.forEach(channel => {
                      if (channel && typeof channel.close === 'function') {
                        channel.close();
                      }
                    });
                    activeChannels = [];
                  });
                }
                
                // 2. Handle WebLocks
                if (window.navigator && navigator.locks) {
                  try {
                    // Create a non-functional locks API that doesn't interfere with bfcache
                    const dummyLocks = {
                      request: function(name, options, callback) {
                        // Handle different parameter patterns
                        if (typeof options === 'function') {
                          callback = options;
                          options = {};
                        }
                        
                        // Call callback immediately with dummy lock
                        if (typeof callback === 'function') {
                          Promise.resolve().then(() => {
                            callback({ name: 'dummy-lock' });
                          });
                        }
                        
                        return Promise.resolve();
                      },
                      query: function() {
                        return Promise.resolve({ held: [] });
                      }
                    };
                    
                    // Replace the locks API
                    Object.defineProperty(navigator, 'locks', {
                      configurable: true,
                      enumerable: true,
                      get: function() {
                        return dummyLocks;
                      }
                    });
                  } catch (e) {
                    console.warn('Failed to patch WebLocks API:', e);
                  }
                }
                
                // 3. Cleanup any other problematic resources on page hide
                const cleanupOnPageHide = function() {
                  // Close any open IndexedDB connections
                  if (window.indexedDB && indexedDB.databases) {
                    try {
                      indexedDB.databases().then(databases => {
                        databases.forEach(database => {
                          try {
                            indexedDB.deleteDatabase(database.name);
                          } catch (e) {
                            // Ignore errors
                          }
                        });
                      }).catch(() => {
                        // Ignore errors
                      });
                    } catch (e) {
                      // Ignore errors
                    }
                  }
                  
                  // Cancel any pending fetch requests
                  if (window.AbortController && typeof AbortController.prototype.abort === 'function') {
                    if (window._pendingFetches) {
                      window._pendingFetches.forEach(controller => {
                        try {
                          controller.abort();
                        } catch (e) {
                          // Ignore errors
                        }
                      });
                      window._pendingFetches = [];
                    }
                  }
                  
                  // Attempt to release any MediaStream tracks
                  if (navigator.mediaDevices) {
                    if (window._activeMediaStreams) {
                      window._activeMediaStreams.forEach(stream => {
                        try {
                          stream.getTracks().forEach(track => track.stop());
                        } catch (e) {
                          // Ignore errors
                        }
                      });
                      window._activeMediaStreams = [];
                    }
                  }
                };
                
                // Register cleanup for back/forward cache
                window.addEventListener('pagehide', cleanupOnPageHide);
                
                // Track fetch requests to ensure they can be aborted
                if (window.fetch && window.AbortController) {
                  const originalFetch = window.fetch;
                  window._pendingFetches = window._pendingFetches || [];
                  
                  window.fetch = function(...args) {
                    const controller = new AbortController();
                    const signal = controller.signal;
                    window._pendingFetches.push(controller);
                    
                    // Add signal to request if not already present
                    if (args[1] && typeof args[1] === 'object') {
                      args[1] = { ...args[1], signal };
                    } else {
                      args[1] = { signal };
                    }
                    
                    return originalFetch(...args).finally(() => {
                      // Remove from pending fetches on completion
                      window._pendingFetches = window._pendingFetches.filter(c => c !== controller);
                    });
                  };
                }
                
                // Track MediaStreams
                if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
                  const originalGetUserMedia = navigator.mediaDevices.getUserMedia;
                  window._activeMediaStreams = window._activeMediaStreams || [];
                  
                  navigator.mediaDevices.getUserMedia = function(...args) {
                    return originalGetUserMedia.apply(this, args).then(stream => {
                      window._activeMediaStreams.push(stream);
                      return stream;
                    });
                  };
                }
                
                // Signal to browsers that we've optimized for bfcache
                if (document.createEvent) {
                  try {
                    // Create and dispatch a custom event to indicate bfcache support
                    const event = document.createEvent('CustomEvent');
                    event.initCustomEvent('BFCacheOptimized', true, true, {
                      optimized: true,
                      timestamp: Date.now()
                    });
                    document.dispatchEvent(event);
                  } catch (e) {
                    // Ignore errors
                  }
                }
              })();
            `
          }}
        />
      </body>
    </Html>
  )
}