[PR #354] [MERGED] fix: render global-error.tsx without root layout to avoid double <html> tags #504

Closed
opened 2026-05-06 13:08:26 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/354
Author: @NathanDrake2406
Created: 3/8/2026
Status: Merged
Merged: 3/8/2026
Merged by: @james-elicx

Base: mainHead: fix/global-error-parity


📝 Commits (1)

  • cf3e3a1 fix: render global-error.tsx without root layout to avoid double <html> tags

📊 Changes

2 files changed (+147 additions, -76 deletions)

View changed files

📝 packages/vinext/src/server/app-dev-server.ts (+94 -51)
📝 tests/nextjs-compat/global-error.test.ts (+53 -25)

📄 Description

Summary

  • Fixed double <html>/<body> tags when global-error.tsx renders as the last-resort error boundary. It provides its own document shell (matching Next.js behavior), so wrapping it inside the root layout produced malformed HTML.
  • Strengthened test assertions in tests/nextjs-compat/global-error.test.ts — previously only checked status < 600 and html.length > 0, now asserts HTTP 200, rendered content, and single <html>/<body> per response.

Three changes to app-dev-server.ts:

  1. buildPageElement: Removed global ErrorBoundary wrapping — it can't work for SSR because the root layout's HTML is already serialized in the RSC stream before the boundary activates, producing nested <html> tags.
  2. Request handler: Added conditional wrapping for RSC requests only — client-side navigation still gets the ErrorBoundary for error recovery after hydration.
  3. renderErrorBoundaryPage: Tracks _isGlobalError flag when error resolution falls through to global-error.tsx, and skips layout wrapping when it does.

Next.js test reference

Ported from: test/e2e/app-dir/global-error/basic/index.test.ts

Test plan

  • pnpm test tests/nextjs-compat/global-error.test.ts — 7/7 pass
  • pnpm test tests/app-router.test.ts — 209/209 pass (no regressions)
  • pnpm test tests/features.test.ts — 231/231 pass (no regressions)
  • pnpm run typecheck — clean
  • pnpm run lint — 0 warnings, 0 errors
  • CI: full Vitest + Playwright E2E

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/354 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 3/8/2026 **Status:** ✅ Merged **Merged:** 3/8/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/global-error-parity` --- ### 📝 Commits (1) - [`cf3e3a1`](https://github.com/cloudflare/vinext/commit/cf3e3a143efabce419b9d664688957b8f438c0af) fix: render global-error.tsx without root layout to avoid double <html> tags ### 📊 Changes **2 files changed** (+147 additions, -76 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/server/app-dev-server.ts` (+94 -51) 📝 `tests/nextjs-compat/global-error.test.ts` (+53 -25) </details> ### 📄 Description ## Summary - **Fixed double `<html>/<body>` tags** when `global-error.tsx` renders as the last-resort error boundary. It provides its own document shell (matching Next.js behavior), so wrapping it inside the root layout produced malformed HTML. - **Strengthened test assertions** in `tests/nextjs-compat/global-error.test.ts` — previously only checked `status < 600` and `html.length > 0`, now asserts HTTP 200, rendered content, and single `<html>/<body>` per response. ### Three changes to `app-dev-server.ts`: 1. **`buildPageElement`**: Removed global ErrorBoundary wrapping — it can't work for SSR because the root layout's HTML is already serialized in the RSC stream before the boundary activates, producing nested `<html>` tags. 2. **Request handler**: Added conditional wrapping for RSC requests only — client-side navigation still gets the ErrorBoundary for error recovery after hydration. 3. **`renderErrorBoundaryPage`**: Tracks `_isGlobalError` flag when error resolution falls through to `global-error.tsx`, and skips layout wrapping when it does. ### Next.js test reference Ported from: [`test/e2e/app-dir/global-error/basic/index.test.ts`](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/global-error/basic/index.test.ts) ## Test plan - [x] `pnpm test tests/nextjs-compat/global-error.test.ts` — 7/7 pass - [x] `pnpm test tests/app-router.test.ts` — 209/209 pass (no regressions) - [x] `pnpm test tests/features.test.ts` — 231/231 pass (no regressions) - [x] `pnpm run typecheck` — clean - [x] `pnpm run lint` — 0 warnings, 0 errors - [x] CI: full Vitest + Playwright E2E --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:08:26 +02:00
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
starred/vinext#504
No description provided.