Skip to content

fix(app-router): propagate unstable_rootParams to actions and route handlers (#1452)#1652

Merged
james-elicx merged 1 commit into
mainfrom
fix/issue-1452-unstable-root-params-propagation
May 28, 2026
Merged

fix(app-router): propagate unstable_rootParams to actions and route handlers (#1452)#1652
james-elicx merged 1 commit into
mainfrom
fix/issue-1452-unstable-root-params-propagation

Conversation

@james-elicx
Copy link
Copy Markdown
Member

Summary

Fixes #1452. setRootParams was only invoked after the post-action route match, so server actions, progressive (form) actions, and route handlers all ran before the unified request context's rootParams slice was populated. Calls to unstable_rootParams() (the next/root-params getters) from those contexts saw undefined instead of the matched root layout's params.

The fix seeds setRootParams from a pre-action route match against the current cleanPathname, so:

  • Route handlers observe rootParams during dispatch.
  • Server actions observe rootParams while the action runs and during the page rerender that follows a successful action.
  • "use cache" functions invoked from any of those contexts read rootParams from the unified request context.

The legacy post-rewrite setRootParams call is preserved as a refresh in case afterFiles/fallback rewrites land on a different route (which can have a different set of root param names).

Tests

Added four targeted tests in tests/app-rsc-handler.test.ts under createAppRscHandler > root params propagation (issue #1452):

  1. Populates root params before route-handler dispatch.
  2. Populates root params before server-action dispatch.
  3. Populates root params before progressive (form) action dispatch.
  4. Only picks root params declared on the matched route (not arbitrary dynamic segments).

Confirmed all four tests fail on main without the production-code change (the route-handler test still passes because the legacy post-match setRootParams runs before route-handler dispatch in the no-rewrite path; the two action tests fail). After the fix all four pass.

Test plan

  • pnpm test tests/app-rsc-handler.test.ts — 28 passed
  • pnpm test tests/root-params.test.ts tests/app-request-context.test.ts — 10 passed
  • pnpm test tests/app-router.test.ts — 331 passed
  • pnpm test tests/features.test.ts — 309 passed
  • vp check packages/vinext/src/ — no format/lint/type errors

References

  • Next.js implementation: .nextjs-ref/packages/next/src/server/request/root-params.ts
  • Next.js tests: .nextjs-ref/test/e2e/app-dir/app-root-params-getters/simple.test.ts

…andlers (#1452)

`setRootParams` was only called after the post-action route match, so
server actions, progressive (form) actions, and route handlers all ran
before the unified request context's `rootParams` slice was populated.
User code calling `unstable_rootParams()` from those contexts therefore
saw `undefined` instead of the matched root layout's params.

Seed `setRootParams` from a pre-action route match against the current
cleanPathname so that:
- route handlers observe rootParams during dispatch
- server actions observe rootParams while the action runs and during
  the page rerender that follows a successful action
- `"use cache"` functions invoked from any of those contexts read
  rootParams from the unified request context

The legacy post-rewrite `setRootParams` call is preserved so the value
is refreshed if `afterFiles`/`fallback` rewrites land on a different
route (and consequently a different set of root param names).
@james-elicx
Copy link
Copy Markdown
Member Author

/bigbonk review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 28, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@1652

commit: 162034d

@github-actions
Copy link
Copy Markdown

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
web preview production

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped fix. The production change is minimal and the test coverage is thorough.

What the fix does right:

  • Seeds setRootParams from a preActionMatch against the post-beforeFiles cleanPathname, which is the correct point in the pipeline — after middleware rewrites and beforeFiles rewrites have been applied, but before action dispatch.
  • Reuses preActionMatch as the initial value for let match at line 544 (let match = preActionMatch), avoiding a redundant second matchRoute call. This is a nice efficiency gain since the match result was already computed.
  • The post-rewrite setRootParams at line 636 remains as a refresh, correctly using the final resolved route's rootParamNames if afterFiles/fallback rewrites land on a different route.
  • The no-match case (when preActionMatch is null) is safe because createRequestContext initializes rootParams to null and each request runs in its own AsyncLocalStorage scope — no cross-request leakage.

Tests:

All four test cases target distinct dispatch paths (route handler, server action, progressive action, root-param filtering) and use dynamic import of the root-params shim inside the mock callbacks to read from the live unified request context. This is a solid testing pattern for verifying request-scoped state. The fourth test (only picks declared root params) is a particularly useful regression guard.

One minor observation (not blocking): the comment at line 499 says "The route is matched against the pre-rewrite cleanPathname here" — this is slightly imprecise since cleanPathname has already been mutated by middleware rewrites (line 429) and beforeFiles rewrites (line 454) by this point. It's really the "pre-afterFiles/fallback-rewrite" pathname. The intent is clear from context though.

LGTM.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 28, 2026

Review posted and PR approved. Here's a summary:

Verdict: Approved

The fix correctly addresses #1452 by eagerly seeding setRootParams from a pre-action route match, ensuring unstable_rootParams() is populated before route handler dispatch, server action dispatch, and progressive action dispatch — all of which previously ran before the old post-action setRootParams call.

Key points from the review:

  • The preActionMatch is computed at the right point in the pipeline (after middleware + beforeFiles rewrites, before action dispatch)
  • Reusing preActionMatch as the initial match value avoids a redundant matchRoute call
  • The no-match case is safe because createRequestContext initializes rootParams to null per request
  • The post-rewrite setRootParams at line 636 correctly refreshes root params if afterFiles/fallback rewrites change the matched route
  • All 28 tests pass, including the 4 new targeted tests

I left one minor non-blocking observation about a slightly imprecise comment ("pre-rewrite" when beforeFiles rewrites have already been applied).

github run

@james-elicx james-elicx marked this pull request as ready for review May 28, 2026 13:42
@james-elicx james-elicx merged commit 9f3ae31 into main May 28, 2026
38 checks passed
@james-elicx james-elicx deleted the fix/issue-1452-unstable-root-params-propagation branch May 28, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App Router: unstable_rootParams not propagated to actions, route handlers, use cache

1 participant