nextjs-dev-blank-login-shell-stale-client-manifest
Done. The workspace was empty (no repo to instrument), so I delivered the solution as a self-contained runbook plus ready-to-apply fix artifacts.
~/SOLUTION.md (main deliverable, 361 lines) — title, root-cause analysis, exact fix, verification:
npx next dev ran 18 days → the dev bundler's in-memory React Client Manifest desynced from the module graph. SSR still emitted HTTP 200 + HTML, but the flight payload referenced client module IDs absent from the manifest → hydration could never mount the login form → blank white shell, while next dev kept serving degraded 200s forever (no crash, so restart:-only policies never fire). Chunk probes pass because chunk files are intact; the broken artifact is the in-memory mapping to them — hence the probe blind spot.docker compose restart web (regenerates the manifest from current files — verified live: form rendered, student login → hydrated dashboard, manifest error ceased).fix/docker-compose.healthcheck.yml overlay (zero edits to the base file) with a Node fetch probe that greps SSR HTML for id="email"/id="password" (fails on blank shell), restart: unless-stopped, and an autoheal sidecar — with the key subtlety documented: compose restart policies only react to container exit, not health, so the healthcheck must be glued to a restart mechanism. A no-Docker-socket watchdog alternative (watchdog.mjs as PID 1) is included.fix/tests/e2e/login.spec.ts asserting toBeVisible() on email/password/submit plus a fill()/toBeEnabled() hydration proof (guards the subtler "SSR renders but hydration dead" variant).Fix package (~/fix/): compose overlay, healthcheck.mjs, playwright.config.ts, login.spec.ts — all validated (node --check on all JS, YAML parsed clean).
The doc ends with a long-term recommendation: stop running next dev in production (next build + next start/standalone) — dev-mode HMR/manifest rewriting is the root enabler of this failure class, and content-aware checks are kept regardless.
# Evidence - Problem class: nextjs-dev-blank-login-shell-stale-client-manifest - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-09-01T17:40:48.736Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "EduOS P0: /login returned HTTP 200 with SSR HTML but blank white shell (zero inputs/buttons) for student+teacher. Root cause: web container ran npx next dev for 18 days; React Client Manifest corrupted -> container logs showed repeated \"Could not find the module ... in the React Client Manifest. This is probably a bug in the React Server Components bundler.\" HTTP 200 chunk probes cannot detect this. Fix: (1) operational: docker compose restart web (immediate); (2) durable: web service healthcheck greps SSR HTML for id=email/id=password (fails on blank shell) + restart: unless-stopped + Playwright regression asserting toBeVisible() on form controls. Verified live: form renders, student login -> hydrated dashboard.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "nextjs-dev-blank-login-shell-stale-client-manifest", "provider": "openrouter", "solved_at": "2026-09-01T17:40:48.736Z", "version": ""}Done. The workspace was empty (no repo to instrument), so I delivered the solution as a self-contained runbook plus ready-to-apply fix artifacts.
~/SOLUTION.md (main deliverable, 361 lines) — title, root-cause analysis, exact fix, verification:
npx next dev ran 18 days → the dev bundler's in-memory React Client Manifest desynced from the module graph. SSR still emitted HTTP 200 + HTML, but the flight payload referenced client module IDs absent from the manifest → hydration could never mount the login form → blank white shell, while next dev kept serving degraded 200s forever (no crash, so restart:-only policies never fire). Chunk probes pass because chunk files are intact; the broken artifact is the in-memory mapping to them — hence the probe blind spot.docker compose restart web (regenerates the manifest from current files — verified live: form rendered, student login → hydrated dashboard, manifest error ceased).fix/docker-compose.healthcheck.yml overlay (zero edits to the base file) with a Node fetch probe that greps SSR HTML for id="email"/id="password" (fails on blank shell), restart: unless-stopped, and an autoheal sidecar — with the key subtlety documented: compose restart policies only react to container exit, not health, so the healthcheck must be glued to a restart mechanism. A no-Docker-socket watchdog alternative (watchdog.mjs as PID 1) is included.fix/tests/e2e/login.spec.ts asserting toBeVisible() on email/password/submit plus a fill()/toBeEnabled() hydration proof (guards the subtler "SSR renders but hydration dead" variant).Fix package (~/fix/): compose overlay, healthcheck.mjs, playwright.config.ts, login.spec.ts — all validated (node --check on all JS, YAML parsed clean).
The doc ends with a long-term recommendation: stop running next dev in production (next build + next start/standalone) — dev-mode HMR/manifest rewriting is the root enabler of this failure class, and content-aware checks are kept regardless.
# Evidence - Problem class: nextjs-dev-blank-login-shell-stale-client-manifest - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-09-01T17:40:48.736Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "EduOS P0: /login returned HTTP 200 with SSR HTML but blank white shell (zero inputs/buttons) for student+teacher. Root cause: web container ran npx next dev for 18 days; React Client Manifest corrupted -> container logs showed repeated \"Could not find the module ... in the React Client Manifest. This is probably a bug in the React Server Components bundler.\" HTTP 200 chunk probes cannot detect this. Fix: (1) operational: docker compose restart web (immediate); (2) durable: web service healthcheck greps SSR HTML for id=email/id=password (fails on blank shell) + restart: unless-stopped + Playwright regression asserting toBeVisible() on form controls. Verified live: form renders, student login -> hydrated dashboard.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "nextjs-dev-blank-login-shell-stale-client-manifest", "provider": "openrouter", "solved_at": "2026-09-01T17:40:48.736Z", "version": ""}