From affe105f7923319c017af8c1eea90a8f73cf7a27 Mon Sep 17 00:00:00 2001 From: Federico Date: Mon, 31 Aug 2026 19:13:52 -0300 Subject: [PATCH] Auto-recargar la app cuando un deploy deja un chunk viejo cacheado MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Después de cada deploy, un navegador con el shell viejo en caché puede intentar cargar un chunk de JS (ej. el de una página lazy-loaded) que el nuevo deploy ya no sirve. Eso tiraba la pantalla genérica de error ("Algo salió mal") en vez de resolverse solo. Ahora el ErrorBoundary detecta ese tipo de error específico y recarga la página una vez automáticamente (con un flag en sessionStorage para no loopear si el problema persiste). Mientras tanto muestra un spinner en vez del cartel de error. --- src/components/ErrorBoundary.jsx | 42 +++++++++++++++++++++++++++ src/components/ErrorBoundary.test.jsx | 36 +++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/components/ErrorBoundary.jsx b/src/components/ErrorBoundary.jsx index a65878f..3828cde 100644 --- a/src/components/ErrorBoundary.jsx +++ b/src/components/ErrorBoundary.jsx @@ -1,5 +1,30 @@ import { Component } from 'react'; import { i18n } from '../i18n'; +import { Spinner } from './ui/Spinner'; + +/** Firefox/Safari/Chrome error strings para módulos JS que un deploy nuevo dejó sin servir. */ +const CHUNK_LOAD_ERROR_PATTERN = + /fetch dynamically imported module|loading dynamically imported module|loading chunk|importing a module script failed/i; + +const CHUNK_RELOAD_FLAG = 'winbit_chunk_reload_attempted'; + +const isChunkLoadError = (error) => CHUNK_LOAD_ERROR_PATTERN.test(error?.message ?? ''); + +const alreadyTriedReload = () => { + try { + return globalThis?.sessionStorage?.getItem(CHUNK_RELOAD_FLAG) === '1'; + } catch { + return false; + } +}; + +const markReloadAttempted = () => { + try { + globalThis?.sessionStorage?.setItem(CHUNK_RELOAD_FLAG, '1'); + } catch { + // ignore storage errors + } +}; export class ErrorBoundary extends Component { constructor(props) { @@ -13,10 +38,27 @@ export class ErrorBoundary extends Component { componentDidCatch(error, errorInfo) { console.error('Error boundary caught:', error, errorInfo); + + // Deploy nuevo invalidó un chunk que el navegador tenía cacheado: recargar + // una vez arregla solo, sin mostrar la pantalla de error al usuario. + if (isChunkLoadError(error) && !alreadyTriedReload()) { + markReloadAttempted(); + globalThis?.location?.reload(); + } } render() { if (this.state.hasError) { + // Primer chunk error: componentDidCatch ya va a recargar la página. + // Mostrar un spinner en vez del cartel de error evita el flash feo. + if (isChunkLoadError(this.state.error) && !alreadyTriedReload()) { + return ( +
+ +
+ ); + } + return (
diff --git a/src/components/ErrorBoundary.test.jsx b/src/components/ErrorBoundary.test.jsx index cb16bea..ce0a435 100644 --- a/src/components/ErrorBoundary.test.jsx +++ b/src/components/ErrorBoundary.test.jsx @@ -6,9 +6,14 @@ const ThrowError = () => { throw new Error('Test error'); }; +const ThrowChunkError = () => { + throw new Error('Failed to fetch dynamically imported module: /assets/DashboardPage-xyz.js'); +}; + describe('ErrorBoundary', () => { beforeEach(() => { vi.spyOn(console, 'error').mockImplementation(() => {}); + sessionStorage.clear(); }); afterEach(() => { @@ -33,4 +38,35 @@ describe('ErrorBoundary', () => { expect(screen.getByText('Algo salió mal')).toBeInTheDocument(); expect(screen.getByText('Recargar página')).toBeInTheDocument(); }); + + it('auto-reloads once on a stale chunk load error instead of showing the error card', () => { + const reloadSpy = vi.fn(); + const locationSpy = vi + .spyOn(globalThis, 'location', 'get') + .mockReturnValue({ reload: reloadSpy }); + + render( + + + , + ); + + expect(reloadSpy).toHaveBeenCalledTimes(1); + expect(sessionStorage.getItem('winbit_chunk_reload_attempted')).toBe('1'); + expect(screen.queryByText('Algo salió mal')).not.toBeInTheDocument(); + + locationSpy.mockRestore(); + }); + + it('falls back to the error card if a chunk error persists after the reload attempt', () => { + sessionStorage.setItem('winbit_chunk_reload_attempted', '1'); + + render( + + + , + ); + + expect(screen.getByText('Algo salió mal')).toBeInTheDocument(); + }); });