[PR #938] [MERGED] fix(app-router): scope layout params and layout error boundaries #965

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

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/938
Author: @NathanDrake2406
Created: 4/28/2026
Status: Merged
Merged: 4/29/2026
Merged by: @james-elicx

Base: mainHead: nathan/app-layout-boundaries-params


📝 Commits (3)

  • bf053b9 fix(app-router): scope layout params and propagate layout head errors
  • 9d9e98e test(app-router): cover layout viewport error boundaries
  • bc6ae85 chore(app-router): address head review nits

📊 Changes

29 files changed (+1045 additions, -813 deletions)

View changed files

📝 packages/vinext/src/entries/app-rsc-entry.ts (+65 -107)
📝 packages/vinext/src/routing/app-router.ts (+10 -0)
📝 packages/vinext/src/server/app-page-boundary-render.ts (+7 -62)
📝 packages/vinext/src/server/app-page-boundary.ts (+42 -3)
packages/vinext/src/server/app-page-head.ts (+141 -0)
packages/vinext/src/server/app-page-params.ts (+53 -0)
📝 packages/vinext/src/server/app-page-route-wiring.tsx (+8 -2)
📝 packages/vinext/src/shims/metadata.tsx (+1 -1)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+324 -636)
📝 tests/app-page-boundary.test.ts (+25 -2)
tests/app-page-head.test.ts (+76 -0)
tests/app-page-params.test.ts (+47 -0)
📝 tests/app-page-route-wiring.test.ts (+39 -0)
📝 tests/app-router.test.ts (+20 -0)
tests/fixtures/app-basic/app/nextjs-compat/layout-forbidden-boundary/layout.tsx (+9 -0)
tests/fixtures/app-basic/app/nextjs-compat/layout-forbidden-boundary/page.tsx (+3 -0)
tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-with-boundary/error.tsx (+9 -0)
tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-with-boundary/layout.tsx (+11 -0)
tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-with-boundary/page.tsx (+3 -0)
tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-without-boundary/layout.tsx (+11 -0)

...and 9 more files

📄 Description

What this changes

App Router layouts now receive only the params that apply at their segment depth during both page tree wiring and layout probing. Layout metadata and viewport resolution use the same segment-scoped params, while page metadata still receives full route params and searchParams.

Layout generateMetadata() and generateViewport() failures now propagate to the normal App Router error boundary path instead of being swallowed. Layout-thrown forbidden() and unauthorized() now select the matching parent HTTP access boundary instead of falling through the not-found-only path.

The generated RSC entry stays thin: it serializes route imports and delegates param slicing, head resolution, search param collection, and parent access-boundary selection to typed server helpers.

Why

Next.js scopes layout params by walking accumulated parent params per segment, not by passing the full route params object to every layout. Relevant references:

Next.js also routes metadata and HTTP access fallback failures through the relevant error/access boundary paths:

Approach

Add small functional helpers under server/ for segment param slicing, search param collection, head resolution, and parent access-boundary selection. The RSC entry now imports those helpers and keeps the route-specific imperative work in place.

The scanner now keeps forbidden and unauthorized boundary arrays aligned with layout levels, matching the existing not-found array shape.

Validation

  • vp test run tests/app-page-params.test.ts tests/app-page-head.test.ts tests/app-page-boundary.test.ts tests/app-page-route-wiring.test.ts tests/entry-templates.test.ts tests/routing.test.ts
  • vp test run tests/nextjs-compat/global-error.test.ts
  • vp test run tests/app-router.test.ts -t "thrown from a layout uses|layout generateMetadata\(\) does not receive searchParams"
  • vp check tests/app-page-params.test.ts tests/app-page-head.test.ts tests/app-page-boundary.test.ts tests/app-page-route-wiring.test.ts tests/entry-templates.test.ts tests/routing.test.ts tests/nextjs-compat/global-error.test.ts tests/app-router.test.ts
  • vp check tests/nextjs-compat/global-error.test.ts
  • git diff --check

Risks / follow-ups

Checked open PRs before publishing. Closest adjacent work is #891, #735, and #822; this PR should not overlap their behavior directly.

This PR intentionally does not address remaining App Router opportunities like unknown Server Action IDs, non-action method handling, route-handler NextResponse.next() validation, or route-handler cookie precedence.


🔄 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/938 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/28/2026 **Status:** ✅ Merged **Merged:** 4/29/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `nathan/app-layout-boundaries-params` --- ### 📝 Commits (3) - [`bf053b9`](https://github.com/cloudflare/vinext/commit/bf053b91f61f735bf37eba8b35a769497dbcb45b) fix(app-router): scope layout params and propagate layout head errors - [`9d9e98e`](https://github.com/cloudflare/vinext/commit/9d9e98e211ae949bfdc173f3562289720984ca91) test(app-router): cover layout viewport error boundaries - [`bc6ae85`](https://github.com/cloudflare/vinext/commit/bc6ae850f482bdb2dbf587142c0b2ad603fdf272) chore(app-router): address head review nits ### 📊 Changes **29 files changed** (+1045 additions, -813 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+65 -107) 📝 `packages/vinext/src/routing/app-router.ts` (+10 -0) 📝 `packages/vinext/src/server/app-page-boundary-render.ts` (+7 -62) 📝 `packages/vinext/src/server/app-page-boundary.ts` (+42 -3) ➕ `packages/vinext/src/server/app-page-head.ts` (+141 -0) ➕ `packages/vinext/src/server/app-page-params.ts` (+53 -0) 📝 `packages/vinext/src/server/app-page-route-wiring.tsx` (+8 -2) 📝 `packages/vinext/src/shims/metadata.tsx` (+1 -1) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+324 -636) 📝 `tests/app-page-boundary.test.ts` (+25 -2) ➕ `tests/app-page-head.test.ts` (+76 -0) ➕ `tests/app-page-params.test.ts` (+47 -0) 📝 `tests/app-page-route-wiring.test.ts` (+39 -0) 📝 `tests/app-router.test.ts` (+20 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/layout-forbidden-boundary/layout.tsx` (+9 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/layout-forbidden-boundary/page.tsx` (+3 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-with-boundary/error.tsx` (+9 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-with-boundary/layout.tsx` (+11 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-with-boundary/page.tsx` (+3 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/layout-metadata-error-without-boundary/layout.tsx` (+11 -0) _...and 9 more files_ </details> ### 📄 Description ## What this changes App Router layouts now receive only the params that apply at their segment depth during both page tree wiring and layout probing. Layout metadata and viewport resolution use the same segment-scoped params, while page metadata still receives full route params and searchParams. Layout `generateMetadata()` and `generateViewport()` failures now propagate to the normal App Router error boundary path instead of being swallowed. Layout-thrown `forbidden()` and `unauthorized()` now select the matching parent HTTP access boundary instead of falling through the not-found-only path. The generated RSC entry stays thin: it serializes route imports and delegates param slicing, head resolution, search param collection, and parent access-boundary selection to typed server helpers. ## Why Next.js scopes layout params by walking accumulated parent params per segment, not by passing the full route params object to every layout. Relevant references: - [Next.js layout params e2e](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/layout-params/layout-params.test.ts) - [Next.js app index layout params coverage](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/app/index.test.ts) - [Next.js create-component-tree param accumulation](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/create-component-tree.tsx) - [Next.js metadata resolution](https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/metadata/resolve-metadata.ts) - [Next.js metadata and viewport outlet error propagation](https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/metadata/metadata.tsx) Next.js also routes metadata and HTTP access fallback failures through the relevant error/access boundary paths: - [Next.js global-error e2e](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/global-error/basic/index.test.ts) - [Next.js forbidden e2e](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/forbidden/basic/forbidden-basic.test.ts) - [Next.js unauthorized e2e](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/unauthorized/basic/unauthorized-basic.test.ts) - [Next.js HTTPAccessFallbackBoundary](https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/http-access-fallback/error-boundary.tsx) ## Approach Add small functional helpers under `server/` for segment param slicing, search param collection, head resolution, and parent access-boundary selection. The RSC entry now imports those helpers and keeps the route-specific imperative work in place. The scanner now keeps forbidden and unauthorized boundary arrays aligned with layout levels, matching the existing not-found array shape. ## Validation - `vp test run tests/app-page-params.test.ts tests/app-page-head.test.ts tests/app-page-boundary.test.ts tests/app-page-route-wiring.test.ts tests/entry-templates.test.ts tests/routing.test.ts` - `vp test run tests/nextjs-compat/global-error.test.ts` - `vp test run tests/app-router.test.ts -t "thrown from a layout uses|layout generateMetadata\(\) does not receive searchParams"` - `vp check tests/app-page-params.test.ts tests/app-page-head.test.ts tests/app-page-boundary.test.ts tests/app-page-route-wiring.test.ts tests/entry-templates.test.ts tests/routing.test.ts tests/nextjs-compat/global-error.test.ts tests/app-router.test.ts` - `vp check tests/nextjs-compat/global-error.test.ts` - `git diff --check` ## Risks / follow-ups Checked open PRs before publishing. Closest adjacent work is [#891](https://github.com/cloudflare/vinext/pull/891), [#735](https://github.com/cloudflare/vinext/pull/735), and [#822](https://github.com/cloudflare/vinext/pull/822); this PR should not overlap their behavior directly. This PR intentionally does not address remaining App Router opportunities like unknown Server Action IDs, non-action method handling, route-handler `NextResponse.next()` validation, or route-handler cookie precedence. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:17 +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#965
No description provided.