cloudflare / cloudflare/vinext

App Router search-only navigation can fail to commit when removing the last query parameter

Open
#2,865 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
8.8k
Forks
406
Avg merge
2d 6h
Merged PRs (30d)
120

Description

## Summary

Under vinext 1.0.0-beta.5, an App Router transition from a filtered URL such as `/top?provider=8` back to the same pathname without search parameters can fail to commit. The RSC request for `/top` reaches the Worker and returns 200, but the client remains in the provider-filtered state or falls into vinext's default client error boundary.

This looks related to the stale search-param cache behavior tracked in #1621, but it is occurring on beta.5 after that issue was closed.

## Reproduction shape

The page is dynamic and reads `searchParams`. A client filter uses `useSearchParams()`, optimistic state, and `router.push()`:

```tsx
"use client";

import { useOptimistic, useTransition } from "react";
import { useRouter, useSearchParams } from "next/navigation";

export function ProviderToggle() {
const router = useRouter();
const routeSearchParams = useSearchParams();
const [isPending, startTransition] = useTransition();
const [optimisticQuery, setOptimisticQuery] = useOptimistic(
routeSearchParams.toString(),
(_current, next: string) => next,
);

const active = new URLSearchParams(optimisticQuery).get("provider") === "8";

return (
{
const params = new URLSearchParams(optimisticQuery);
if (active) params.delete("provider");
else params.set("provider", "8");

const query = params.toString();
startTransition(() => {
setOptimisticQuery(query);
router.push(query ? `/top?${query}` : "/top", { scroll: false });
});
}}
>
Netflix

);
}
```

## Steps

1. Load `/top`.
2. Click the provider toggle. The navigation to `/top?provider=8` commits and filtered server content renders.
3. Click the active provider toggle again. This calls `router.push("/top", { scroll: false })`.
4. Observe the transition.

## Actual behavior

The request sequence includes a successful `GET /top?_rsc=...` response, but the navigation can fail to commit on the client. The provider remains active / the filtered tree remains mounted. During the same production session, the client also reached vinext's default `This page couldn’t load` boundary without a server digest.

## Expected behavior

The transition commits to `/top`, `useSearchParams()` becomes empty for `provider`, and the unfiltered server tree replaces the filtered tree.

## Notes

- This was observed in a real Cloudflare Workers deployment immediately after migrating an existing Next.js App Router application to vinext.
- A fresh isolated browser did not reproduce it every time, so a cache or in-flight navigation race may be involved.
- The server-side RSC request succeeded, which points to client navigation/cache commit rather than the route's data fetch.
- #1621 describes a closely related stale-search-param cache-key failure, so this may be a regression or an uncovered transition path.

## Environment

- vinext: 1.0.0-beta.5
- Next.js: 16.2.10
- React / React DOM: 19.2.8
- Runtime: Cloudflare Workers

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the supplied ProviderToggle flow: navigate from `/top` to `/top?provider=8`, then remove the last query parameter; compare the successful RSC response with the client’s committed route state. Read the related issue #1621 and trace the client navigation/cache commit path. Done means the transition reliably commits to `/top`, clears the provider search parameter, and renders the unfiltered server tree.

Written by the indexing model from the issue text.

Assessment

Tech stack
next.js, react, typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.