Skip to content

Flag dev static indicator as Dynamic when route reads searchParams#92605

Draft
yogeshwaran-c wants to merge 1 commit intovercel:canaryfrom
yogeshwaran-c:fix/dev-static-indicator-search-params
Draft

Flag dev static indicator as Dynamic when route reads searchParams#92605
yogeshwaran-c wants to merge 1 commit intovercel:canaryfrom
yogeshwaran-c:fix/dev-static-indicator-search-params

Conversation

@yogeshwaran-c
Copy link
Copy Markdown

What?

Fixes the dev static indicator reporting a route as Static when the page reads searchParams. It now correctly reports Dynamic, matching how the same route is treated at build time.

Why?

searchParams is a dynamic API in the app router — awaiting it (or accessing a property on the resolved value) forces the route to render dynamically. However, createRenderSearchParams in dev did not update requestStore.usedDynamic, so the dev indicator — which derives its state from isStatic = !usedDynamic && !forceDynamic in app-render.tsx — showed Static even though next build correctly marked the same route dynamic. This caused confusing dev/build drift for users of searchParams.

How?

cookies() and headers() already flip usedDynamic via trackDynamicDataInDynamicRender() at their call site. searchParams can't do the same at its creation site because createServerSearchParamsForServerPage runs for every server component page, whether the page uses searchParams or not — tracking there would over-report every page as dynamic.

Instead, the dev proxy traps in makeUntrackedSearchParamsWithDevWarnings now call trackDynamicDataInDynamicRender(requestStore):

  • On the resolved-object proxy (instrumentSearchParamsObjectWithDevWarnings): any get / has / ownKeys access that occurs after promiseInitialized.current === true. This is the path that fires only when user code does const sp = await searchParams; sp.a (or similar), so framework probes are excluded.
  • On the promise proxy (instrumentSearchParamsPromiseWithDevWarnings): sync misuse paths (property / in / Object.keys on the promise itself without awaiting). Left the .then trap alone to avoid counting framework internals as dynamic access.

trackDynamicDataInDynamicRender is already a no-op in production, so the change is dev-indicator-only at runtime.

Test plan

Added a new search-params/page.tsx fixture plus a test case to test/development/app-dir/dev-indicator/route-type.test.ts that reproduces the original bug (reported as Static on canary without the fix). The existing static / force-dynamic / headers cases in the same suite still pass, confirming no over-reporting.

Also ran the adjacent suites that exercise searchParams behavior to catch regressions:

  • test/development/app-dir/async-request-warnings/async-request-warnings.test.ts — 7/7 pass (includes sync searchParams access warning).
  • test/e2e/app-dir/searchparams-static-bailout/searchparams-static-bailout.test.ts — 5/5 pass (covers both server and client component searchParams).

Fixes #72133

In app router dev, awaiting `searchParams` in a page did not update
`requestStore.usedDynamic`, so the dev static indicator reported the
route as Static even though it would render dynamically at build time.

Hook property access on the resolved searchParams proxy (and the sync
misuse path on the promise proxy) to flip `usedDynamic` via
`trackDynamicDataInDynamicRender`, matching how `cookies()` and
`headers()` already track dynamic usage. Framework `.then` probes on
the promise proxy are left untouched to avoid over-reporting.

Fixes vercel#72133
@nextjs-bot
Copy link
Copy Markdown
Collaborator

Allow CI Workflow Run

  • approve CI run for commit: d6ae800

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Static indicator shouldn't be shown when searchParams is being used

2 participants