[PR #578] test: expand Next.js compat coverage and fix parity bugs #685

Open
opened 2026-05-06 13:09:31 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/578
Author: @dmmulroy
Created: 3/17/2026
Status: 🔄 Open

Base: mainHead: autoresearch/nextjs-compat-audit-2026-03-17


📝 Commits (10+)

  • 5fdfb94 autoresearch: setup compat test audit loop
  • 5b5b084 Baseline: 233 passing compat tests, 21 files, 13 dirs covered. Fixed autoresearch.sh ANSI stripping.\n\nResult: {"status":"keep","passing_compat_tests":233,"test_files":21,"dirs_covered":13,"skipped_tests":2}
  • 129b6ed Port app-fetch-deduping-errors: 3 tests for page rendering despite fetch errors in generateMetadata and page component\n\nResult: {"status":"keep","passing_compat_tests":236,"test_files":22,"dirs_covered":14,"skipped_tests":2}
  • 9342d34 Port forbidden: 5 tests for forbidden() boundary, 403 status, scoped + root escalation. Also triaged 6 P1 dirs as skip (Playwright/CLI/build-specific).\n\nResult: {"status":"keep","passing_compat_tests":241,"test_files":23,"dirs_covered":15,"skipped_tests":2}
  • 17473c7 Port unauthorized: 5 tests for unauthorized() boundary, 401 status, scoped + root escalation. Skip proxy-missing-export (covered by PR #203).\n\nResult: {"status":"keep","passing_compat_tests":246,"test_files":24,"dirs_covered":16,"skipped_tests":2}
  • 59bd8bc Port catch-all-optional: 3 tests for optional catch-all routing. Batch-triage 14 more P1+P2 dirs as skip. 249 total tests.\n\nResult: {"status":"keep","passing_compat_tests":249,"test_files":25,"dirs_covered":17,"skipped_tests":2}
  • 5a03d6d Port simple-routes + FIX BUG: route handlers received plain Request instead of NextRequest (.nextUrl undefined). Wrapped in NextRequest in app-rsc-entry.ts.\n\nResult: {"status":"keep","passing_compat_tests":251,"test_files":26,"dirs_covered":18,"skipped_tests":2}
  • 3979dbe Port rsc-redirect: 3 tests for redirect() from server component. Found RSC redirect behavioral difference (307 vs 200+stream). Triage 4 more dirs.\n\nResult: {"status":"keep","passing_compat_tests":254,"test_files":27,"dirs_covered":19,"skipped_tests":2}
  • bff4433 Port static-generation-status: 5 tests for notFound/redirect/permanentRedirect status codes + client component redirect SSR. Triage 5 more dirs.\n\nResult: {"status":"keep","passing_compat_tests":259,"test_files":28,"dirs_covered":20,"skipped_tests":2}
  • baf1790 Port layout-params: 6 tests. BUG FOUND+FIXED: layouts received ALL params instead of scoped per-segment params. Fixed 4 rendering loops in app-rsc-entry.ts + buildPageElement. Added cheerio + fetchDom helper.\n\nResult: {"status":"keep","passing_compat_tests":265,"test_files":29,"dirs_covered":21,"skipped_tests":2}

📊 Changes

124 files changed (+3675 additions, -111 deletions)

View changed files

📝 package.json (+1 -0)
📝 packages/vinext/src/entries/app-rsc-entry.ts (+99 -15)
📝 packages/vinext/src/entries/pages-server-entry.ts (+26 -2)
📝 packages/vinext/src/server/middleware.ts (+33 -2)
📝 packages/vinext/src/shims/headers.ts (+1 -1)
📝 pnpm-lock.yaml (+365 -5)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+504 -86)
tests/fixtures/app-basic/app/nextjs-compat/api/edge.json/route.ts (+13 -0)
tests/fixtures/app-basic/app/nextjs-compat/api/node.json/route.ts (+11 -0)
tests/fixtures/app-basic/app/nextjs-compat/catch-all-optional/[lang]/[flags]/[[...rest]]/page.tsx (+21 -0)
tests/fixtures/app-basic/app/nextjs-compat/conflicting-params/api/[id]/route.ts (+11 -0)
tests/fixtures/app-basic/app/nextjs-compat/conflicting-params/render/[id]/page.tsx (+34 -0)
tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/client-page/page.tsx (+28 -0)
tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/force-dynamic/page.tsx (+42 -0)
tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/force-static/page.tsx (+42 -0)
tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/getSentinelValue.tsx (+11 -0)
tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/layout.tsx (+17 -0)
tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/top-level/page.tsx (+40 -0)
tests/fixtures/app-basic/app/nextjs-compat/fetch-deduping-errors/[id]/page.tsx (+39 -0)
tests/fixtures/app-basic/app/nextjs-compat/forbidden-basic/dynamic-no-boundary/[id]/page.tsx (+18 -0)

...and 80 more files

📄 Description

Summary

This PR ports a large batch of HTTP-testable Next.js App Router compatibility coverage into tests/nextjs-compat/ and fixes several real runtime parity bugs uncovered along the way.

Highlights:

  • ports additional Next.js compat coverage across routing, request APIs, metadata routes, redirects, middleware, and cache behavior
  • adds several dedicated fixtures for cases that could not be expressed cleanly in the shared fixture
  • fixes request-scoped/runtime parity bugs discovered while porting tests
  • intentionally excludes autoresearch assets from the PR diff; the key findings are summarized below

Included coverage

Notable compat additions in this branch include:

  • fetch-deduping-errors
  • forbidden
  • unauthorized
  • optional catch-all routing
  • simple route handlers
  • RSC redirects
  • static generation status behavior
  • layout param scoping
  • dynamic metadata file routes
  • searchParams static bailout coverage
  • useParams
  • conflicting route/search param names
  • underscored root directory behavior
  • route-handler-only use cache
  • dynamic request API behavior
  • rewrites/redirects edge cases
  • route handlers with trailingSlash
  • default not-found behavior
  • middleware request/header/draft-mode/cache coverage

Runtime fixes uncovered by the compat ports

  • wrap App Router route-handler requests in NextRequest so req.nextUrl exists
  • scope layout params per segment instead of leaking all route params to every layout
  • prevent force-static pages from receiving live searchParams
  • render default 404 UI through normal layout wrapping when no explicit not-found.tsx exists
  • run middleware with a next/headers context and capture draft-mode cookies before the ALS scope unwinds

Validation

Primary validation for this branch came from the compat benchmark loop plus the automatic backpressure checks required by autoresearch mode.

Latest kept result before removing autoresearch assets from the branch:

  • passing compat tests: 312
  • compat test files: 40
  • covered Next.js dirs: 31
  • skipped tests: 2
Autoresearch findings summary

Bugs found and fixed

  1. Route handlers received plain Request instead of NextRequest, so req.nextUrl was undefined.
  2. Layout params scoping was broken: layouts received all route params instead of only the params for their segment subtree.
  3. force-static pages still saw live searchParams.
  4. Default 404 rendering skipped root/ancestor layouts when no explicit not-found.tsx existed.
  5. Middleware had no next/headers request context, and middleware draft-mode cookies could be lost after the ALS scope unwound.

Behavioral differences documented

  • RSC redirects currently return an HTTP redirect in vinext, whereas Next.js encodes the redirect into the RSC payload for client handling.

Process/tooling improvements from the loop

  • added fetchDom/Cheerio-based assertions for more DOM-structured compat tests
  • used dedicated fixtures where the shared fixture would have hidden important parity gaps
Autoresearch metric progression
  • Baseline: 233 passing compat tests
  • Final kept branch result: 312 passing compat tests
  • Net gain: +79 passing compat tests
Autoresearch notes intentionally left out of the diff

This PR intentionally excludes files such as:

  • autoresearch.md
  • autoresearch.sh
  • autoresearch.checks.sh
  • autoresearch.manifest.json
  • autoresearch.jsonl
  • autoresearch.ideas.md

Those assets were useful during the audit loop but are not needed to review or merge the runtime/test changes.

Reviewer notes

This branch is best reviewed commit-by-commit or by file grouping:

  1. compat tests/fixtures
  2. runtime fixes in packages/vinext/src/**
  3. generated-entry snapshot updates

🔄 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/578 **Author:** [@dmmulroy](https://github.com/dmmulroy) **Created:** 3/17/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `autoresearch/nextjs-compat-audit-2026-03-17` --- ### 📝 Commits (10+) - [`5fdfb94`](https://github.com/cloudflare/vinext/commit/5fdfb944f06aa1c6236e173620740145540675f4) autoresearch: setup compat test audit loop - [`5b5b084`](https://github.com/cloudflare/vinext/commit/5b5b084d61fcebd02f5c3ff91b60aa0d774b2418) Baseline: 233 passing compat tests, 21 files, 13 dirs covered. Fixed autoresearch.sh ANSI stripping.\n\nResult: {"status":"keep","passing_compat_tests":233,"test_files":21,"dirs_covered":13,"skipped_tests":2} - [`129b6ed`](https://github.com/cloudflare/vinext/commit/129b6ed9fea204ca0d70f148fd27b00127aae687) Port app-fetch-deduping-errors: 3 tests for page rendering despite fetch errors in generateMetadata and page component\n\nResult: {"status":"keep","passing_compat_tests":236,"test_files":22,"dirs_covered":14,"skipped_tests":2} - [`9342d34`](https://github.com/cloudflare/vinext/commit/9342d34f4e338830be41f56b195c767171631032) Port forbidden: 5 tests for forbidden() boundary, 403 status, scoped + root escalation. Also triaged 6 P1 dirs as skip (Playwright/CLI/build-specific).\n\nResult: {"status":"keep","passing_compat_tests":241,"test_files":23,"dirs_covered":15,"skipped_tests":2} - [`17473c7`](https://github.com/cloudflare/vinext/commit/17473c744642dc345e1c9923df421036e2b12005) Port unauthorized: 5 tests for unauthorized() boundary, 401 status, scoped + root escalation. Skip proxy-missing-export (covered by PR #203).\n\nResult: {"status":"keep","passing_compat_tests":246,"test_files":24,"dirs_covered":16,"skipped_tests":2} - [`59bd8bc`](https://github.com/cloudflare/vinext/commit/59bd8bc1873f2c87fdd7f601440c55b1c44207a1) Port catch-all-optional: 3 tests for optional catch-all routing. Batch-triage 14 more P1+P2 dirs as skip. 249 total tests.\n\nResult: {"status":"keep","passing_compat_tests":249,"test_files":25,"dirs_covered":17,"skipped_tests":2} - [`5a03d6d`](https://github.com/cloudflare/vinext/commit/5a03d6db0db1266f8eacca9184065513dbf4a6be) Port simple-routes + FIX BUG: route handlers received plain Request instead of NextRequest (.nextUrl undefined). Wrapped in NextRequest in app-rsc-entry.ts.\n\nResult: {"status":"keep","passing_compat_tests":251,"test_files":26,"dirs_covered":18,"skipped_tests":2} - [`3979dbe`](https://github.com/cloudflare/vinext/commit/3979dbe2cb6d954fb56b94fd050b8de1ba94a8ce) Port rsc-redirect: 3 tests for redirect() from server component. Found RSC redirect behavioral difference (307 vs 200+stream). Triage 4 more dirs.\n\nResult: {"status":"keep","passing_compat_tests":254,"test_files":27,"dirs_covered":19,"skipped_tests":2} - [`bff4433`](https://github.com/cloudflare/vinext/commit/bff443391815a84d6a8b265d886c395d863fd67c) Port static-generation-status: 5 tests for notFound/redirect/permanentRedirect status codes + client component redirect SSR. Triage 5 more dirs.\n\nResult: {"status":"keep","passing_compat_tests":259,"test_files":28,"dirs_covered":20,"skipped_tests":2} - [`baf1790`](https://github.com/cloudflare/vinext/commit/baf179035b2feb1b0940c240d707384e0a30e766) Port layout-params: 6 tests. BUG FOUND+FIXED: layouts received ALL params instead of scoped per-segment params. Fixed 4 rendering loops in app-rsc-entry.ts + buildPageElement. Added cheerio + fetchDom helper.\n\nResult: {"status":"keep","passing_compat_tests":265,"test_files":29,"dirs_covered":21,"skipped_tests":2} ### 📊 Changes **124 files changed** (+3675 additions, -111 deletions) <details> <summary>View changed files</summary> 📝 `package.json` (+1 -0) 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+99 -15) 📝 `packages/vinext/src/entries/pages-server-entry.ts` (+26 -2) 📝 `packages/vinext/src/server/middleware.ts` (+33 -2) 📝 `packages/vinext/src/shims/headers.ts` (+1 -1) 📝 `pnpm-lock.yaml` (+365 -5) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+504 -86) ➕ `tests/fixtures/app-basic/app/nextjs-compat/api/edge.json/route.ts` (+13 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/api/node.json/route.ts` (+11 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/catch-all-optional/[lang]/[flags]/[[...rest]]/page.tsx` (+21 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/conflicting-params/api/[id]/route.ts` (+11 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/conflicting-params/render/[id]/page.tsx` (+34 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/client-page/page.tsx` (+28 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/force-dynamic/page.tsx` (+42 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/force-static/page.tsx` (+42 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/getSentinelValue.tsx` (+11 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/layout.tsx` (+17 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/dynamic-data/top-level/page.tsx` (+40 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/fetch-deduping-errors/[id]/page.tsx` (+39 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/forbidden-basic/dynamic-no-boundary/[id]/page.tsx` (+18 -0) _...and 80 more files_ </details> ### 📄 Description ## Summary This PR ports a large batch of HTTP-testable Next.js App Router compatibility coverage into `tests/nextjs-compat/` and fixes several real runtime parity bugs uncovered along the way. Highlights: - ports additional Next.js compat coverage across routing, request APIs, metadata routes, redirects, middleware, and cache behavior - adds several dedicated fixtures for cases that could not be expressed cleanly in the shared fixture - fixes request-scoped/runtime parity bugs discovered while porting tests - intentionally excludes autoresearch assets from the PR diff; the key findings are summarized below ## Included coverage Notable compat additions in this branch include: - `fetch-deduping-errors` - `forbidden` - `unauthorized` - optional catch-all routing - simple route handlers - RSC redirects - static generation status behavior - layout param scoping - dynamic metadata file routes - `searchParams` static bailout coverage - `useParams` - conflicting route/search param names - underscored root directory behavior - route-handler-only `use cache` - dynamic request API behavior - rewrites/redirects edge cases - route handlers with `trailingSlash` - default `not-found` behavior - middleware request/header/draft-mode/cache coverage ## Runtime fixes uncovered by the compat ports - wrap App Router route-handler requests in `NextRequest` so `req.nextUrl` exists - scope layout params per segment instead of leaking all route params to every layout - prevent `force-static` pages from receiving live `searchParams` - render default 404 UI through normal layout wrapping when no explicit `not-found.tsx` exists - run middleware with a `next/headers` context and capture draft-mode cookies before the ALS scope unwinds ## Validation Primary validation for this branch came from the compat benchmark loop plus the automatic backpressure checks required by autoresearch mode. Latest kept result before removing autoresearch assets from the branch: - passing compat tests: **312** - compat test files: **40** - covered Next.js dirs: **31** - skipped tests: **2** <details> <summary>Autoresearch findings summary</summary> ### Bugs found and fixed 1. Route handlers received plain `Request` instead of `NextRequest`, so `req.nextUrl` was undefined. 2. Layout params scoping was broken: layouts received all route params instead of only the params for their segment subtree. 3. `force-static` pages still saw live `searchParams`. 4. Default 404 rendering skipped root/ancestor layouts when no explicit `not-found.tsx` existed. 5. Middleware had no `next/headers` request context, and middleware draft-mode cookies could be lost after the ALS scope unwound. ### Behavioral differences documented - RSC redirects currently return an HTTP redirect in vinext, whereas Next.js encodes the redirect into the RSC payload for client handling. ### Process/tooling improvements from the loop - added `fetchDom`/Cheerio-based assertions for more DOM-structured compat tests - used dedicated fixtures where the shared fixture would have hidden important parity gaps </details> <details> <summary>Autoresearch metric progression</summary> - Baseline: **233** passing compat tests - Final kept branch result: **312** passing compat tests - Net gain: **+79** passing compat tests </details> <details> <summary>Autoresearch notes intentionally left out of the diff</summary> This PR intentionally excludes files such as: - `autoresearch.md` - `autoresearch.sh` - `autoresearch.checks.sh` - `autoresearch.manifest.json` - `autoresearch.jsonl` - `autoresearch.ideas.md` Those assets were useful during the audit loop but are not needed to review or merge the runtime/test changes. </details> ## Reviewer notes This branch is best reviewed commit-by-commit or by file grouping: 1. compat tests/fixtures 2. runtime fixes in `packages/vinext/src/**` 3. generated-entry snapshot updates --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
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#685
No description provided.