Repository: vercel/next.js
File: packages/next/src/server/app-render/vary-params.ts (TypeScript)

Code before:

export function createVaryingParams(
  accumulator: VaryParamsAccumulator,
  originalParamsObject: Params
): Params {
  const underlyingParamsWithVarying: Params = {}
  for (const paramName in originalParamsObject) {
    Object.defineProperty(underlyingParamsWithVarying, paramName, {
      get() {
        accumulateVaryParam(accumulator, paramName)
        return originalParamsObject[paramName]
      },
      enumerable: true,
    })
  }
  return underlyingParamsWithVarying
}

export function createVaryingSearchParams(
  accumulator: VaryParamsAccumulator,
  originalSearchParamsObject: SearchParams
): SearchParams {
  const underlyingSearchParamsWithVarying: SearchParams = {}
  for (const searchParamName in originalSearchParamsObject) {
    Object.defineProperty(underlyingSearchParamsWithVarying, searchParamName, {
      get() {
        // TODO: Unlike path params, we don't vary track each search param
        // individually. The entire search string is treated as a single param.
        // This may change in the future.
        accumulateVaryParam(accumulator, '?')
        return originalSearchParamsObject[searchParamName]
      },
      enumerable: true,
    })
  }
  return underlyingSearchParamsWithVarying
}

/**
 * Resolves all segment accumulators in a ResponseVaryParamsAccumulator with
 * their final vary params. Call this after rendering is complete.
 *
 * Each segment's thenable is resolved with its vary params merged with the
 * root params. If we can't track vary params (e.g., legacy prerender), simply
 * don't call this function - the client treats unresolved thenables as
 * "unknown" vary params.
 */
export async function finishAccumulatingVaryParams(
  responseAccumulator: ResponseVaryParamsAccumulator
): Promise<void> {
  const rootVaryParams = responseAccumulator.rootParams.varyParams

Review comment (suggestion):
I initially wondered if this should also track existing params (like `[category]` in a `[category]/[[...slug]]` route), matching the `hasOwnProperty` check in the `get` trap. But it doesn't need to. Required params are always present on the target object, so their `in` result is always `true` and can never vary between requests. Only the optional catch-all's existence can differ. Same reasoning applies to the `ownKeys` trap.

Code after:

export function createVaryingParams(
  accumulator: VaryParamsAccumulator,
  originalParamsObject: Params,
  optionalCatchAllParamName: string | null
): Params {
  if (optionalCatchAllParamName !== null) {
    // When there's an optional catch-all param with no value (e.g.,
    // [[...slug]] at /), the param doesn't exist as a property on the params
    // object. Use a Proxy to track all param access — both existing params
    // and the missing optional param — including enumeration patterns like
    // Object.keys(), spread, for...in, and `in` checks.
    return new Proxy(originalParamsObject, {
      get(target, prop, receiver) {
        if (typeof prop === 'string') {
          if (
            prop === optionalCatchAllParamName ||
            Object.prototype.hasOwnProperty.call(target, prop)
          ) {
            accumulateVaryParam(accumulator, prop)
          }
        }
        return Reflect.get(target, prop, receiver)
      },
      has(target, prop) {
        if (prop === optionalCatchAllParamName) {
          accumulateVaryParam(accumulator, optionalCatchAllParamName)
        }
        return Reflect.has(target, prop)
      },
      ownKeys(target) {
        // Enumerating the params object means the user's code may depend on
        // which params are present, so conservatively track the optional
        // param as accessed.
        accumulateVaryParam(accumulator, optionalCatchAllParamName)
        return Reflect.ownKeys(target)
      },
    })
  }

  // When there's no optional catch-all, all params exist as properties on the
  // object, so we can use defineProperty getters instead of a Proxy. This is
  // faster because the engine can optimize property access on regular objects
  // more aggressively than Proxy trap calls.
  const underlyingParamsWithVarying: Params = {}
  for (const paramName in originalParamsObject) {
    Object.defineProperty(underlyingParamsWithVarying, paramName, {
      get() {
        accumulateVaryParam(accumulator, paramName)
        return originalParamsObject[paramName]
      },