Fix dev shell validation for novel parameter values - #98460
Draft
gnoff wants to merge 1 commit into
Draft
Conversation
Contributor
Failing test suitesCommit: 36e32f2 | About building and testing Next.js
Expand output● instant-nav-panel › history traversals › re-arms the capture when navigating back from a captured SPA navigation |
gnoff
added a commit
that referenced
this pull request
Sep 9, 2026
## Summary Add experimental parameter-matching APIs behind `experimental.paramMatching` so Cache Components can separate build-time prerender selection from runtime cache-miss behavior. - `generateStaticParams` continues to select concrete parameter combinations to prerender during the build. - `experimental_paramMatching` and `experimental_generateParamMatching` configure each visible route parameter as `not-found`, `blocking`, `fallback`, or permanently `dynamic`. - Parameters without an explicit directive retain the existing static-shell heuristics. ## Stack Builds on #98460, which restores value-independent dev shell validation without introducing an API. This layer adds explicit policy boundaries on top of that baseline; the shared render/validation separation is owned by the base PR. ## Example For `/[lang]/catalog/[top]/items/[bottom]`: ```ts // app/[lang]/layout.tsx export const experimental_paramMatching = { lang: 'not-found', } as const // app/[lang]/catalog/[top]/items/[bottom]/page.tsx export const experimental_paramMatching = { top: 'blocking', bottom: 'fallback', } as const export function generateStaticParams() { return [{ lang: 'en', top: 't1', bottom: 'b1' }] } ``` This makes an unknown `lang` a 404, blocks while generating a novel `top`, immediately serves fallback UI for a novel `bottom`, and retains `/en/catalog/t1/items/b1` as a build-time prerender. ## Composition and validation - A layout or page may configure only parameters visible at or above its own segment. - Static and generated fragments run independently and are merged from ancestors toward the page using descendant-wins assignment. - Next.js validates the merged explicit policy exactly as written; it never rewrites neighboring inherited directives to make an override coherent. - Parameters must progress in `not-found -> blocking -> fallback -> dynamic` order. - Parallel branches that contribute to the same URL matcher must agree on shared parameter policy and produce a coherent combined policy. - A parameter configured as `dynamic` cannot participate in `generateStaticParams` output. - After explicit policy is merged and validated, unconfigured holes are filled using existing build-time shell inference. ## Static-shell behavior An explicit `blocking` or `fallback` directive does not require `generateStaticParams` to provide an example for that parameter. The build validates the most-specific generic shell reachable from the available examples: - Explicit fallback shells must be non-empty and are retained for immediate use on a miss. - Generic blocking shells may be rendered only for validation and are never registered as concrete cache outputs. - A more-specific generated shell supersedes an unnecessary generic blocking validation. - `instant = false` opts out of the static-shell requirement. Development uses the same specificity model while anonymizing literal parameter values: it considers whether each parameter is present, not whether that value appeared during the build. Ordinary development requests remain dynamic renders rather than entering the production prerender path. ## Deployment and coverage Closed `not-found` prefixes reuse the existing fallback-false adapter contract, so the initial implementation works with the current Vercel routing layer without a proxy change. The fixture matrix covers inherited and overridden policy, inferred holes, no-example blocking and fallback behavior, catch-all and optional catch-all routes, parallel slots, foreground cache behavior, development validation specificity, export constraints, type generation, and invalid configurations. The optional local matcher diagnostic remains isolated in the following commit so it can be removed before release. ## Verification - `pnpm build-all` - `pnpm --filter=next types` - `pnpm jest packages/next/src/build/static-paths/app.test.ts --runInBand` - `pnpm test-dev-turbo test/e2e/app-dir/generate-prerender-matching/generate-prerender-matching.test.ts` - `pnpm test-start-turbo test/e2e/app-dir/generate-prerender-matching/generate-prerender-matching.test.ts` - `pnpm test-start-webpack test/e2e/app-dir/generate-prerender-matching/generate-prerender-matching.test.ts`
gnoff
added this pull request to stack #98461
September 9, 2026 22:02
## Summary Fix Cache Components development validation so it checks the most-specific parameter shape produced by `generateStaticParams`, regardless of the literal parameter values in the request. This is a standalone fix for existing behavior, with no experimental matching API or feature flag. It forms the bottom of the parameter-matching stack. #97393 builds on this separation to let explicit fallback directives choose an earlier validation boundary, so this fix can land without committing to the new API. ## Why The dev server currently chooses fallback parameters by matching the requested URL against the generated paths. That is appropriate for staging the foreground response, but that same selection also reaches background static-shell validation. A novel value therefore causes validation to check a more generic shell than the build requires. For `/[top]/items/[bottom]`, suppose `generateStaticParams` returns: ```ts return [{ top: 't1' }] ``` The build validates `/t1/items/[bottom]`: `top` is available, while `bottom` remains dynamic. A dev request for `/t2/items/b2` should exercise the same shape using its actual `top: 't2'` value. It should not require the shell to work with both `top` and `bottom` unknown. | Generated example | Dev request | Unknown parameters during static-shell validation | | --- | --- | --- | | `{ top: 't1', bottom: 'b1' }` | `/t2/items/b2` | None | | `{ top: 't1' }` | `/t2/items/b2` | `bottom` | | No examples | `/t2/items/b2` | `top`, `bottom` | This restores the value-independent validation semantics that were lost when #95066 made foreground staging value-sensitive. It does not revert that foreground staging change. ## Changes - Select the smallest fallback-parameter set across all generated paths for validation, separately from the per-URL set used by the foreground render. - Pass that validation shape through dev-only request metadata. - When the two shapes differ, construct a separate validation context and request store. Do not reuse Flight chunks produced with the foreground parameter shape; use the existing background validation render instead. - Require an explicit render context at every dev payload call site, rather than defaulting to a captured foreground context or introducing a separate validation wrapper. - Keep production rendering, build output, cache-miss routing, and the foreground dev response unchanged. - Strengthen the existing no-API fixture to wait for background validation before asserting success. Restore the novel-`top` error expectation to the page that reads the genuinely dynamic `bottom`, rather than the layout that reads `top`. - Limit this fixture to static-shell validation so automatic navigation-boundary insights do not obscure the behavior under test. ## Verification - Reproduced two failing regression cases against the unchanged baseline, then passed the suite with the fix. - `CI=1 pnpm build-all` (uses the installed native package) - `pnpm --filter=next types` - ESLint and Prettier for the changed files - `pnpm test-dev-webpack test/development/app-dir/cache-components-dev-fallback-validation/cache-components-dev-fallback-validation.test.ts` - `pnpm test-dev-turbo test/development/app-dir/cache-components-dev-fallback-validation/cache-components-dev-fallback-validation.test.ts` - `pnpm test-dev-webpack test/development/app-dir/cache-components-dev-warmup -t 'mixed static and fallback params resolve in the correct phase'` (24 checks across eight configurations; also passed against the unchanged baseline) <!-- NEXT_JS_LLM -->
gnoff
added a commit
that referenced
this pull request
Sep 9, 2026
## Summary Add experimental parameter-matching APIs behind `experimental.paramMatching` so Cache Components can separate build-time prerender selection from runtime cache-miss behavior. - `generateStaticParams` continues to select concrete parameter combinations to prerender during the build. - `experimental_paramMatching` and `experimental_generateParamMatching` configure each visible route parameter as `not-found`, `blocking`, `fallback`, or permanently `dynamic`. - Parameters without an explicit directive retain the existing static-shell heuristics. ## Stack Builds on #98460, which restores value-independent dev shell validation without introducing an API. This layer adds explicit policy boundaries on top of that baseline; the shared render/validation separation is owned by the base PR. ## Example For `/[lang]/catalog/[top]/items/[bottom]`: ```ts // app/[lang]/layout.tsx export const experimental_paramMatching = { lang: 'not-found', } as const // app/[lang]/catalog/[top]/items/[bottom]/page.tsx export const experimental_paramMatching = { top: 'blocking', bottom: 'fallback', } as const export function generateStaticParams() { return [{ lang: 'en', top: 't1', bottom: 'b1' }] } ``` This makes an unknown `lang` a 404, blocks while generating a novel `top`, immediately serves fallback UI for a novel `bottom`, and retains `/en/catalog/t1/items/b1` as a build-time prerender. ## Composition and validation - A layout or page may configure only parameters visible at or above its own segment. - Static and generated fragments run independently and are merged from ancestors toward the page using descendant-wins assignment. - Next.js validates the merged explicit policy exactly as written; it never rewrites neighboring inherited directives to make an override coherent. - Parameters must progress in `not-found -> blocking -> fallback -> dynamic` order. - Parallel branches that contribute to the same URL matcher must agree on shared parameter policy and produce a coherent combined policy. - A parameter configured as `dynamic` cannot participate in `generateStaticParams` output. - After explicit policy is merged and validated, unconfigured holes are filled using existing build-time shell inference. ## Static-shell behavior An explicit `blocking` or `fallback` directive does not require `generateStaticParams` to provide an example for that parameter. The build validates the most-specific generic shell reachable from the available examples: - Explicit fallback shells must be non-empty and are retained for immediate use on a miss. - Generic blocking shells may be rendered only for validation and are never registered as concrete cache outputs. - A more-specific generated shell supersedes an unnecessary generic blocking validation. - `instant = false` opts out of the static-shell requirement. Development uses the same specificity model while anonymizing literal parameter values: it considers whether each parameter is present, not whether that value appeared during the build. Ordinary development requests remain dynamic renders rather than entering the production prerender path. ## Deployment and coverage Closed `not-found` prefixes reuse the existing fallback-false adapter contract, so the initial implementation works with the current Vercel routing layer without a proxy change. The fixture matrix covers inherited and overridden policy, inferred holes, no-example blocking and fallback behavior, catch-all and optional catch-all routes, parallel slots, foreground cache behavior, development validation specificity, export constraints, type generation, and invalid configurations. The optional local matcher diagnostic remains isolated in the following commit so it can be removed before release. ## Verification - `pnpm build-all` - `pnpm --filter=next types` - `pnpm jest packages/next/src/build/static-paths/app.test.ts --runInBand` - `pnpm test-dev-turbo test/e2e/app-dir/generate-prerender-matching/generate-prerender-matching.test.ts` - `pnpm test-start-turbo test/e2e/app-dir/generate-prerender-matching/generate-prerender-matching.test.ts` - `pnpm test-start-webpack test/e2e/app-dir/generate-prerender-matching/generate-prerender-matching.test.ts`
gnoff
force-pushed
the
codex/fix-dev-param-validation
branch
from
September 9, 2026 23:16
12fa6f5 to
36e32f2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix Cache Components development validation so it checks the most-specific parameter shape produced by
generateStaticParams, regardless of the literal parameter values in the request.This is a standalone fix for existing behavior, with no experimental matching API or feature flag. It forms the bottom of the parameter-matching stack. #97393 builds on this separation to let explicit fallback directives choose an earlier validation boundary, so this fix can land without committing to the new API.
Why
The dev server currently chooses fallback parameters by matching the requested URL against the generated paths. That is appropriate for staging the foreground response, but that same selection also reaches background static-shell validation. A novel value therefore causes validation to check a more generic shell than the build requires.
For
/[top]/items/[bottom], supposegenerateStaticParamsreturns:The build validates
/t1/items/[bottom]:topis available, whilebottomremains dynamic. A dev request for/t2/items/b2should exercise the same shape using its actualtop: 't2'value. It should not require the shell to work with bothtopandbottomunknown.{ top: 't1', bottom: 'b1' }/t2/items/b2{ top: 't1' }/t2/items/b2bottom/t2/items/b2top,bottomThis restores the value-independent validation semantics that were lost when #95066 made foreground staging value-sensitive. It does not revert that foreground staging change.
Changes
toperror expectation to the page that reads the genuinely dynamicbottom, rather than the layout that readstop.Verification
CI=1 pnpm build-all(uses the installed native package)pnpm --filter=next typespnpm test-dev-webpack test/development/app-dir/cache-components-dev-fallback-validation/cache-components-dev-fallback-validation.test.tspnpm test-dev-turbo test/development/app-dir/cache-components-dev-fallback-validation/cache-components-dev-fallback-validation.test.tspnpm test-dev-webpack test/development/app-dir/cache-components-dev-warmup -t 'mixed static and fallback params resolve in the correct phase'(24 checks across eight configurations; also passed against the unchanged baseline)