W11-C3: Lazy-loaded page chunks crash to full error boundary when offline — "Try Again" does not re-fetch the chunk, page stays broken #172

Closed
opened 2026-05-20 18:09:34 +00:00 by totdking · 8 comments
totdking commented 2026-05-20 18:09:34 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-05-20 18:34:49 +00:00 (Migrated from gitlab.com)

Issue Summary

When the browser is offline and navigates to a route whose JavaScript chunk has not yet been cached (e.g. /swap, /charts), the dynamic import fails and the app-level ErrorBoundary fires, replacing the entire page with a full-screen "SOMETHING WENT WRONG" overlay. Clicking "TRY AGAIN" only resets the boundary's React state — it does not re-trigger the failed import() — so the error immediately reappears. The trader is permanently stuck until they do a full browser reload.


Reproduction Steps

Precondition: The app must already be loaded in the browser before going offline. If the page is loaded while already offline, Chrome shows its own native offline page (dinosaur) — that is browser-level behaviour, not an app issue.

  1. Start the app (VITE_NETWORK=local npm run dev) and let it fully load on any route (e.g. /trade)
  2. In DevTools Network tab, block requests to 127.0.0.1:3001 (indexer) and set throttling to Offline
  3. Navigate to /swap — a route whose JS chunk has not been loaded yet in this session
  4. Observe: full-screen "SOMETHING WENT WRONG" overlay with technical details: Failed to fetch dynamically imported module: http://localhost:3000/src/pages/SwapPage.tsx
  5. Click TRY AGAIN
  6. Observe: same error reappears immediately — page does not recover

Note: If DevTools is open side-by-side and the page is reloaded while offline, Chrome will show its native dinosaur offline page instead of the React app — this is expected browser behaviour. The React error boundary only fires when the app is already running in memory and then tries to lazy-load a new route chunk.


Expected Behavior

  • Offline lazy-load failures should show a targeted degradation message scoped to the route, not a full-screen crash overlay.
  • "TRY AGAIN" should attempt to re-import the failed chunk (or perform a soft reload) so the trader has a working recovery path without a full browser refresh.

Actual Behavior

  • Suspense lazy loading fails while offline → app-level ErrorBoundary catches it → full-screen overlay shown.
  • "TRY AGAIN" calls this.setState({ hasError: false, error: null }) only — no re-fetch of the failed chunk. Component re-renders and immediately throws again.
  • Technical details exposes internal dev-server URL (http://localhost:3000/src/pages/SwapPage.tsx).

Screen record

Screen Recording 2026-05-20 at 19.31.51.mov


Console Logs

TypeError: Failed to fetch dynamically imported module: http://localhost:3000/src/pages/SwapPage.tsx
[ErrorBoundary] Unhandled error: TypeError: Failed to fetch dynamically imported module

Environment Details

Field Value
OS macOS (Apple M1 Pro, 14-inch)
Browser Google Chrome (desktop)
Viewport ~1440px desktop
Network localterra (local Docker), DevTools Offline mode
Frontend VITE_NETWORK=local npm run dev

Wallet / Device Details

  • Wallet: Station browser extension
  • Pages affected: /swap, /charts (any lazily-loaded route not yet in browser cache)

Severity / Impact

P2 UX. Any trader who navigates to a fresh route during a connectivity drop hits a full-screen crash with a broken recovery button. The expected CEX-level behaviour is either a graceful "unavailable" message scoped to the route or a working retry. Found under W11-C3 (Contract query failures — Interactivity and Trader use dimensions).

cc: @PlasticDigits

### Issue Summary When the browser is offline and navigates to a route whose JavaScript chunk has not yet been cached (e.g. `/swap`, `/charts`), the dynamic import fails and the app-level `ErrorBoundary` fires, replacing the entire page with a full-screen "SOMETHING WENT WRONG" overlay. Clicking "TRY AGAIN" only resets the boundary's React state — it does not re-trigger the failed `import()` — so the error immediately reappears. The trader is permanently stuck until they do a full browser reload. --- ### Reproduction Steps **Precondition:** The app must already be loaded in the browser before going offline. If the page is loaded while already offline, Chrome shows its own native offline page (dinosaur) — that is browser-level behaviour, not an app issue. 1. Start the app (`VITE_NETWORK=local npm run dev`) and let it fully load on any route (e.g. `/trade`) 2. In DevTools Network tab, block requests to `127.0.0.1:3001` (indexer) and set throttling to **Offline** 3. Navigate to `/swap` — a route whose JS chunk has not been loaded yet in this session 4. Observe: full-screen "SOMETHING WENT WRONG" overlay with technical details: `Failed to fetch dynamically imported module: http://localhost:3000/src/pages/SwapPage.tsx` 5. Click **TRY AGAIN** 6. Observe: same error reappears immediately — page does not recover **Note:** If DevTools is open side-by-side and the page is reloaded while offline, Chrome will show its native dinosaur offline page instead of the React app — this is expected browser behaviour. The React error boundary only fires when the app is already running in memory and then tries to lazy-load a new route chunk. --- ### Expected Behavior - Offline lazy-load failures should show a targeted degradation message scoped to the route, not a full-screen crash overlay. - "TRY AGAIN" should attempt to re-import the failed chunk (or perform a soft reload) so the trader has a working recovery path without a full browser refresh. --- ### Actual Behavior - `Suspense` lazy loading fails while offline → app-level `ErrorBoundary` catches it → full-screen overlay shown. - "TRY AGAIN" calls `this.setState({ hasError: false, error: null })` only — no re-fetch of the failed chunk. Component re-renders and immediately throws again. - Technical details exposes internal dev-server URL (`http://localhost:3000/src/pages/SwapPage.tsx`). --- ## Screen record ![Screen Recording 2026-05-20 at 19.31.51.mov](/uploads/867301f92e34b1dd7923e5ce3cf11898/Screen_Recording_2026-05-20_at_19.31.51.mov) --- ### Console Logs ``` TypeError: Failed to fetch dynamically imported module: http://localhost:3000/src/pages/SwapPage.tsx [ErrorBoundary] Unhandled error: TypeError: Failed to fetch dynamically imported module ``` --- ### Environment Details | Field | Value | |-------|-------| | OS | macOS (Apple M1 Pro, 14-inch) | | Browser | Google Chrome (desktop) | | Viewport | \~1440px desktop | | Network | `localterra` (local Docker), DevTools Offline mode | | Frontend | `VITE_NETWORK=local npm run dev` | --- ### Wallet / Device Details - **Wallet:** Station browser extension - **Pages affected:** `/swap`, `/charts` (any lazily-loaded route not yet in browser cache) --- ### Severity / Impact **P2 UX.** Any trader who navigates to a fresh route during a connectivity drop hits a full-screen crash with a broken recovery button. The expected CEX-level behaviour is either a graceful "unavailable" message scoped to the route or a working retry. Found under **W11-C3** (Contract query failures — Interactivity and Trader use dimensions). cc: @PlasticDigits
totdking commented 2026-05-20 20:29:13 +00:00 (Migrated from gitlab.com)

mentioned in issue #116

mentioned in issue #116
PlasticDigits commented 2026-05-21 12:14:57 +00:00 (Migrated from gitlab.com)

mentioned in commit 020d8306ab

mentioned in commit 020d8306abfccce5f001ed174edeedf38d9d8241
PlasticDigits commented 2026-05-21 12:14:59 +00:00 (Migrated from gitlab.com)

mentioned in commit c55efd5328

mentioned in commit c55efd532834b7da8eb5a26647088eebe22f106d
PlasticDigits commented 2026-05-21 12:15:10 +00:00 (Migrated from gitlab.com)

Fix landed on main (020d830)

@totdking — please verify when you can; leaving the issue open until W11-C3 sign-off.

What changed

  • LazyRoute wraps every lazy page route: route-scoped ErrorBoundary (data-testid="route-error-boundary") keeps header/nav visible instead of a full-screen app crash.
  • Try Again now bumps loadAttempt, creating a fresh React.lazy(loader) + import() — not only clearing boundary state (root cause of the stuck retry loop).
  • chunkLoadError.ts classifies dynamic import / chunk failures; humanizeOffChainError maps them to retail copy; technical details scrub dev-server URLs.
  • Chunk failures use headline Page unavailable with humanized body text.
  • Docs: docs/frontend.md § Lazy route chunks; agent playbook: skills/AGENTS_FRONTEND_LAZY_CHUNK_LOAD.md (cross-linked from user-errors + LCD skills).

Verification checklist

  • VITE_NETWORK=local npm run dev — load app on /trade (or any route), wait for full load
  • DevTools → Offline (app must already be in memory; cold offline load is Chrome’s dinosaur page, not this UI)
  • Navigate to an uncached lazy route (e.g. /charts, /pool) — expect Page unavailable inside main content, not full-screen app overlay; header/nav still visible
  • Try Again while still offline — error may return (expected); no instant stuck loop without re-fetch attempt
  • Restore network → Try Again — page loads without full browser reload
  • Technical details (if expanded) must not show localhost:3000/src/pages/... raw dev paths
  • cd frontend-dapp && npm run test:unit — includes chunkLoadError.test.ts + LazyRoute.test.tsx

Automated tests run before merge

  • npm run test:unit (507 tests)
  • npm run build
## Fix landed on `main` (020d830) @totdking — please verify when you can; leaving the issue **open** until W11-C3 sign-off. ### What changed - **`LazyRoute`** wraps every lazy page route: route-scoped **`ErrorBoundary`** (`data-testid="route-error-boundary"`) keeps header/nav visible instead of a full-screen app crash. - **Try Again** now bumps `loadAttempt`, creating a fresh `React.lazy(loader)` + `import()` — not only clearing boundary state (root cause of the stuck retry loop). - **`chunkLoadError.ts`** classifies dynamic import / chunk failures; **`humanizeOffChainError`** maps them to retail copy; technical details scrub dev-server URLs. - Chunk failures use headline **Page unavailable** with humanized body text. - Docs: [docs/frontend.md § Lazy route chunks](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md#lazy-route-chunks); agent playbook: `skills/AGENTS_FRONTEND_LAZY_CHUNK_LOAD.md` (cross-linked from user-errors + LCD skills). ### Verification checklist - [ ] `VITE_NETWORK=local npm run dev` — load app on `/trade` (or any route), wait for full load - [ ] DevTools → **Offline** (app must already be in memory; cold offline load is Chrome’s dinosaur page, not this UI) - [ ] Navigate to an **uncached** lazy route (e.g. `/charts`, `/pool`) — expect **Page unavailable** inside main content, **not** full-screen app overlay; header/nav still visible - [ ] **Try Again** while still offline — error may return (expected); no instant stuck loop without re-fetch attempt - [ ] Restore network → **Try Again** — page loads without full browser reload - [ ] Technical details (if expanded) must **not** show `localhost:3000/src/pages/...` raw dev paths - [ ] `cd frontend-dapp && npm run test:unit` — includes `chunkLoadError.test.ts` + `LazyRoute.test.tsx` ### Automated tests run before merge - `npm run test:unit` (507 tests) - `npm run build`
Brouie commented 2026-05-25 02:16:45 +00:00 (Migrated from gitlab.com)

verified c55efd5 on QA stack.

source: new LazyRoute.tsx wraps lazy chunks in ErrorBoundary + Suspense, retries the dynamic import on Try Again click. chunkLoadError.ts detects fetch failures from import().

tests: chunkLoadError.test.ts 4/4 PASS + LazyRoute.test.tsx 3/3 PASS = 7/7 total. covers retry-after-failure, route-scoped headline, and dynamic import re-run.

stderr noise in the run is the test harness deliberately triggering chunk-load failures so the error boundary can catch them - expected for boundary tests.

good to close on your side @PlasticDigits

verified `c55efd5` on QA stack. source: new `LazyRoute.tsx` wraps lazy chunks in `ErrorBoundary` + `Suspense`, retries the dynamic import on Try Again click. `chunkLoadError.ts` detects fetch failures from `import()`. tests: `chunkLoadError.test.ts` 4/4 PASS + `LazyRoute.test.tsx` 3/3 PASS = 7/7 total. covers retry-after-failure, route-scoped headline, and dynamic import re-run. stderr noise in the run is the test harness deliberately triggering chunk-load failures so the error boundary can catch them - expected for boundary tests. good to close on your side @PlasticDigits
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-25 02:23:54 +00:00
PlasticDigits commented 2026-08-30 02:48:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #706

mentioned in issue #706
PlasticDigits commented 2026-08-30 05:37:10 +00:00 (Migrated from gitlab.com)

mentioned in commit 8c1d1bc5d4

mentioned in commit 8c1d1bc5d4c12306d1aa5f3915ba9457d4aceabf
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#172
No description provided.