# Next 16 Blank Page? Check the Cache First

> Upgraded to 16 and got white screens: stale .next cache, middleware leftovers, proxy mismatches. The clean-and-verify ritual inside.

Source: https://boringpush.com/blog/nextjs-16-blank-page-connection-closed

## Upgraded, deployed, white nothing

Upgrade to 16 went smoothly — build green, deploy clean, open the URL... white. No error, no console screams that explain, just blank. Localhost (fresh clone) worked perfectly. Production served nothing with total confidence.

Redeployed (white), rolled back (colorful, working), re-upgraded (white again). Deterministic blankness: same code, same host, different .next directory contents. The app was fine. Its built artifacts were haunted by the previous major.

Nextjs 16 blank page is almost never your code and almost always your cache: stale .next output, middleware-to-proxy leftovers, mismatched manifests. The fix is ritual, not debugging.

## Blank-page causes ranked by likelihood

| Cause | Likelihood | Fix time |
|---|---|---|
| Stale .next cache (old major artifacts) | ~70% | 2 min (clean + rebuild) |
| Middleware/proxy leftovers | ~15% | 10 min (migrate properly) |
| Manifest/version mismatch | ~10% | 5 min (lockfile + reinstall) |
| Actual code breakage | ~5% | (the corridor posts) |
| DNS/CDN serving old bundle | Rare but real | Purge + verify |

Three things blank pages teach about upgrade hygiene:

1. **Build caches are version-liable.** `.next` output embeds framework internals — major upgrades invalidate assumptions the cache can't see (it caches outputs, not their validity). Stale cache plus new runtime equals white screens with green builds. Caches accelerate steady states and poison transitions; clean at every major, no exceptions.
2. **Middleware ghosts haunt proxy migrations.** Half-migrated middleware (old file present, new proxy partial) produces routing neither handles — requests fall through both into nothing. The proxy migration needs completion (old deleted, new verified), not coexistence. Two routers means no router; finish the move.
3. **Localhost lies by freshness.** Fresh clones build clean (no stale cache), making "works on my machine" technically true and diagnostically useless. CI/prod caches persist across deploys (layer caching, incremental builds) — the exact environments that matter most carry the oldest artifacts. Debug where it fails (prod cache state), not where it's convenient (fresh laptop).

The corridor post covers substantive breaks. This one covers the blankness between them.

## Nextjs 16 blank page ritual

Clean, rebuild, verify — in that order, every upgrade, before any debugging.

**Step 1 — Nuke the build cache completely.**

```bash
# local + CI, scorched earth (2 min, fixes ~70% of blanks):
rm -rf .next node_modules/.cache
npm ci                    # fresh install (lockfile truth, not folder habit)
npm run build             # clean-room build, watch for NEW errors (previously masked)
```

Scorched-earth first because it's cheapest (two minutes) and most likely (seventy percent). Masked errors surfacing in clean builds are bonus diagnoses — the cache wasn't just stale, it was hiding real issues. Clean builds tell the truth; cached builds tell history. Truth first, always.

**Step 2 — Finish the middleware-to-proxy migration.**

```bash
# exactly ONE router file must exist:
ls middleware.* proxy.* 2>/dev/null   # expect: proxy.ts ONLY (or neither, never both)
# matcher audit: proxy matchers cover old middleware routes (no orphan paths)
# delete strays, rebuild, verify routing table explicitly (not "it worked before")
```

Coexistence is the bug: old middleware plus new proxy equals neither routing. Delete decisively (git history remembers), audit matchers against old routes (orphans fall into voids), verify explicitly per path. Migration means moved (old gone, new proven), never doubled. One router, verified, committed.

**Step 3 — Verify production-likeness, not localhost.**

```bash
# staging mirror of prod caching (or prod deploy slot):
# 1. deploy clean build 2. curl key routes (status + non-empty bodies!)
# 3. check manifests match (build-id consistent across edge + origin)
# non-empty bodies is the assertion everyone skips (200 with empty body = white page, green checks)
```

Empty-200 is the silent killer (status green, body void — monitors cheer, users stare). Assert body presence, not just status codes, in smoke tests. Build-ID consistency across edge/origin catches CDN staleness (old bundles served confidently). Verify like a user (content rendered?) plus like an engineer (manifests match?) — both greens or it isn't green.

The promise, kept — upgrades that land colored, never blank.

## The blank-page ledger

| Upgrade | First deploy | Ritual applied | Result |
|---|---|---|---|
| 15.x minor | White (stale .next) | Clean + rebuild | Colored in 4 min |
| 16.0 major | White (middleware ghost) | Proxy migration finished | Colored in 15 min |
| 16.x minor | White (manifest drift) | Lockfile reinstall | Colored in 7 min |
| Total blank time | ~2 hrs confusion (first) | ~26 min ritual (all) |

BoringPush upgrades land in color. Pattern beats panic |

Fair's fair: scorched-earth rebuilds cost minutes per upgrade (full reinstall + clean build vs incremental) plus the discipline of never debugging blanks before cleaning. Priced against confusion-hours plus rollback churn: ritual minutes beat mystery hours, every major, forever. Clean first is a policy, not a suggestion — write it into the upgrade checklist permanently.

## FAQ

### Is nextjs 16 blank page ever actually a code bug?

Rarely (~5%): genuine cases involve root-layout errors (uncaught, boundary-missing) or global-error misconfiguration (the /_global-error threads show real ones). Rule: clean-cache ritual first (2 min, rules out 70%), then code investigation with fresh eyes. Ritual-first ordering isn't laziness — it's probability-ordered debugging. Common first, exotic second, always.

### Should CI cache .next between builds for speed?

Minor versions: yes (real speedups, compatible artifacts). Majors: never (poison guarantee — key caches by lockfile AND framework version, or disable across upgrades). Cache keys must include the framework major, or the cache serves last-major outputs to this-major runtimes. Speed is for steady states; majors get clean rooms. Key wisely or clean always.

### How do I tell CDN staleness from app blankness?

Curl origin directly (bypass CDN: hosts-file or origin hostname) — content present means CDN serves stale (purge + verify), absent means app issue (continue ritual). Two-minute bisection that halves every blank-page investigation. CDN vs origin is always the first fork; guessing wastes the hours bisection saves.

### What belongs in the post-upgrade smoke test?

Status + non-empty body per key route (kills empty-200 class), one authenticated flow (session paths differ), one dynamic route (params/cache paths), build-ID consistency edge-vs-origin. Four checks, two minutes, automated in CI deploy jobs. Smoke tests assert colored pages, not green statuses — the distinction this entire post exists to enforce.

### Do Docker layer caches cause the same blankness?

Yes, same mechanism (stale layers = stale artifacts): `--no-cache` rebuilds on majors, layer keys including framework version otherwise. Container caching amplifies the .next problem (layers persist across deploys by design). Same ritual, container vocabulary: clean layers on majors, keyed layers otherwise. Cache invalidation remains one of the two hard problems — now with containers.

### When is blank-page actually DNS or TLS, not cache?

When nothing loads including assets (total void vs app-shell-only blank), or errors mention certificates/hosts (explicit, unlike cache silence). Check DNS propagation + cert validity in parallel with cache ritual (independent tracks, two minutes each). Layered failures happen (cache AND cert) — parallel tracks prevent sequential blindness. Verify all layers, assume none.

## Keep reading

- [14 to 16 Upgrade: Caching Broke Everything](/blog/nextjs-14-to-16-upgrade-cache-pain) — the corridor this ritual guards
- [Next 16 Async Params Broke My Build: Fix](/blog/nextjs-16-async-params-breaking) — the loud break beside this quiet one
- [Deploy Next.js on Hetzner $5 by Dinner Tonight](/blog/deploy-nextjs-hetzner-vps) — the box these rituals run on
