fix(error-handler): auto-reload em chunk load errors após deploy (stale chunks)#600
Conversation
…load Adds detection of "Failed to fetch dynamically imported module" and related stale-chunk errors that happen when Vercel publishes new asset hashes while users have older tabs open. - isChunkLoadError(error): pattern-matcher for 7 known chunk error signatures + stack-based fallback for unnamed TypeErrors - handleChunkLoadError: triggers 1 (and only 1) auto-reload per session, guarded by sessionStorage to prevent infinite loops - lazyWithRetry: optional wrapper for React.lazy with 1 retry on chunk error (for future use in route components) - useGlobalErrorCatcher: now intercepts chunk errors in both 'error' and 'unhandledrejection' events before they reach the generic toast
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Caution Review failedPull request was closed or merged during review WalkthroughNova infraestrutura em ChangesDetecção e recuperação de stale deploy / chunk load error
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Rationale: Lógica de detecção com padrões regex, gerenciamento de estado em sessionStorage, controle de timers e cleanup, integração dupla em handlers, uso de event.preventDefault, e necessidade de validar edge cases (loops de reload, race conditions com grace period, diferenças entre error vs unhandledrejection). Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a713d1d55
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } catch { | ||
| // sessionStorage indisponível (Safari private etc) — silencia | ||
| } |
There was a problem hiding this comment.
Preserve the reload guard when storage is blocked
When this catch path is hit (for example in a sandboxed frame or a browser/privacy mode that denies sessionStorage writes), the reload attempt is never persisted; getReloadAttempts() will keep returning 0, so any persistent chunk failure is treated as a first attempt and schedules another automatic reload. That defeats the loop protection this handler is meant to provide and can trap affected users in a reload loop instead of showing the manual hard-refresh message.
Useful? React with 👍 / 👎.
| const clearTimer = window.setTimeout(() => { | ||
| clearReloadAttempts(); | ||
| }, CHUNK_RELOAD_CLEAR_MS); |
There was a problem hiding this comment.
Do not clear attempts before lazy chunks are proven healthy
Because this timer clears the guard merely 5 seconds after the shell boots, a deploy with a broken lazy route that is not requested during those first 5 seconds will lose the previous auto-reload count. If the user later navigates to that route, the same persistent chunk error is treated as a fresh first attempt and auto-reloads again instead of stopping after one session attempt, so the advertised broken-deploy fallback can still loop on delayed route access.
Useful? React with 👍 / 👎.
| error instanceof TypeError && | ||
| typeof error.stack === 'string' && | ||
| /\/assets\/[^/]+\.js/.test(error.stack) |
There was a problem hiding this comment.
Narrow the TypeError stack fallback
In production, ordinary application TypeErrors also have stack frames pointing at bundled files under /assets/*.js, so this fallback will classify many real runtime bugs (for example a null dereference inside a lazy page) as stale chunk failures. Those errors then skip the normal global error logging/toast path and trigger an automatic reload, masking the original bug and disrupting users even when no deploy-stale chunk is involved.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds client-side protection against “stale chunk” failures after deploys by detecting known chunk-load error signatures and triggering a guarded auto-reload from the global error listeners, reducing navigation breakage for users with long-lived tabs.
Changes:
- Adds chunk-load error detection heuristics and a sessionStorage-based reload-attempt guard.
- Updates
useGlobalErrorCatcherto intercept chunk-relatederrorandunhandledrejectionevents before the generic toast. - Introduces an optional
lazyWithRetryhelper (in this file) intended for future use.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** | ||
| * Wrapper opcional para React.lazy que tenta novamente uma vez ao falhar. | ||
| * Uso (futuro, em rotas que sofrem com isso): | ||
| * const Page = lazy(lazyWithRetry(() => import('./Page'))); | ||
| */ | ||
| export function lazyWithRetry<T>( | ||
| importFn: () => Promise<T>, | ||
| retries = 1, | ||
| ): () => Promise<T> { | ||
| return async () => { | ||
| try { | ||
| return await importFn(); | ||
| } catch (error) { | ||
| if (!isChunkLoadError(error) || retries <= 0) throw error; | ||
| await new Promise((resolve) => setTimeout(resolve, 800)); | ||
| return await importFn(); | ||
| } | ||
| }; | ||
| } |
| window.setTimeout(() => { | ||
| window.location.reload(); | ||
| }, CHUNK_RELOAD_DELAY_MS); |
| // Carregou com sucesso — limpa o contador de reload após grace period | ||
| const clearTimer = window.setTimeout(() => { | ||
| clearReloadAttempts(); | ||
| }, CHUNK_RELOAD_CLEAR_MS); | ||
|
|
| * Detecta se um erro é causado por chunk JS faltando (stale deploy). | ||
| * Cobre os padrões observados em Chromium, Firefox e Safari. | ||
| */ | ||
| export function isChunkLoadError(error: unknown): boolean { |
🚨 Bug observado em produção (2026-06-02 11:54-11:57 BRT)
Após o merge do PR #599 e o consequente deploy do Vercel, usuários com tabs já abertas começaram a ver falhas de navegação em todas as rotas lazy-loaded:
Rotas afetadas:
/favoritos,/produto/:id,/simulador-precos,/colecoes,/mockup-generator, e qualquer rota comlazy(() => import(...)).Por que aconteceu
Quando o Vercel publicou os novos chunks, os hashes mudaram:
ProductDetail-BZ9BghlM.js→ 404ProductDetail-Xy7AbCdE.jsO cliente, ainda com
index.htmlantigo em memória, tentou buscar o chunk antigo. Vercel respondeu com oindex.html(SPA fallback, 200), e o browser recusou: arquivo.jsveio comtext/html.Solução
useGlobalErrorCatcherjá capturavaunhandledrejectionmas só mostrava o toast genérico "Erro inesperado. Tente recarregar a página." — perdendo a chance de fazer o reload automaticamente.Este PR adiciona detecção específica de chunk error seguida de auto-reload protegido contra loop:
isChunkLoadError(error)— pattern-matcher contra 7 assinaturas conhecidas (Chrome/Firefox/Safari) + fallback heurístico baseado em stack contendo/assets/*.jshandleChunkLoadError()— tenta 1 (e apenas 1) reload por sessão, usandosessionStoragecomo guard. Se já tentou, mostra toast com instrução de hard refresh manual.useGlobalErrorCatcher— agora intercepta chunk errors em ambos os listeners (errorpara falhas de tag<script>,unhandledrejectionparaimport()que rejeita) antes do toast genérico.lazyWithRetry()(bônus) — wrapper opcional paraReact.lazy()que tenta 1 retry com 800ms de delay. Não consumido neste PR, disponível para uso futuro em rotas problemáticas.Padrões cobertos pelo
isChunkLoadErrorFailed to fetch dynamically imported moduleFailed to load module scriptExpected a JavaScript-or-Wasm module scripterror loading dynamically imported moduleImporting a module script failedLoading chunk \w+ failedChunkLoadError/assets/*.jsCenários simulados
sessionStoragelimpa após 5s, contador zeradosessionStorageindisponível (Safari private)try/catchsilencia, segue para o reloadArquivos alterados
src/hooks/ui/useErrorHandler.ts— adicionadosisChunkLoadError,handleChunkLoadError,lazyWithRetry, e branch nos handlers douseGlobalErrorCatcherBugs vistos no console que não são tratados neste PR
Blocked aria-hidden on an element because its descendant retained focus— Radix Dialog/Sheet aplicandoaria-hiddenno<div.min-h-screen>enquanto<main>retém foco. Issue separada.Tooltip is changing from uncontrolled to controlled— algum Tooltip alternandoopenentreundefinedeboolean. Issue separada.Esses são bugs de acessibilidade/warnings, não bloqueiam UX. Vão em PRs próprios após identificar os componentes específicos.
Pós-merge
Após o merge deste PR, o Vercel vai fazer um novo deploy. Usuários com tabs antigas (incluindo as deste novo deploy) vão receber o auto-reload uma única vez e seguir na nova versão. A partir daí, todos os deploys subsequentes terão essa proteção embutida.
Summary by cubic
Detecta erros de carregamento de chunks após deploy e faz auto-reload uma única vez para evitar falhas de navegação em rotas lazy. Usuários com abas antigas passam a atualizar para a nova versão sem loops.
isChunkLoadError: identifica padrões de erro de chunks em Chrome/Firefox/Safari.sessionStorage(1 tentativa por sessão); se falhar, mostra instrução de hard refresh.useGlobalErrorCatcher: intercepta erros emerroreunhandledrejectionantes do toast genérico.lazyWithRetry: wrapper opcional paraReact.lazy()com 1 retry em erro de chunk (não utilizado neste PR).Written for commit 9a713d1. Summary will update on new commits.
Summary by CodeRabbit
Release Notes