-
Notifications
You must be signed in to change notification settings - Fork 30.3k
Description
Link to the code that reproduces this issue
https://github.com/KumarNitin19/nextjs-prefetch-redirect-bug
To Reproduce
I'm running middleware that conditionally redirects based on a cookie value. The / route is protected and redirects to /process when the processingComplete cookie is not set.
Steps to Reproduce:
- Navigate to
/processpage (no cookie set) - Page renders with a sidebar containing
<Link href="/">Dashboard</Link> - Next.js automatically prefetches the
/route - Middleware evaluates and returns 307 redirect to
/process(because cookie is false) - Click "Start Process" button which:
- Performs async work (simulated 2 seconds)
- Sets cookie:
processingComplete=true - Calls
router.push('/')to navigate to dashboard
- Observe: User stuck on
/processpage, no navigation occurs
Live Demo (Vercel): https://nextjs-prefetch-redirect-bug.vercel.app/
Current vs. Expected behavior
Expected:
When router.push('/') is called after the cookie changes:
- Fresh GET request should be made to
/ - Middleware should re-evaluate with new cookie value
- Middleware should allow navigation (no redirect)
- User navigates to Dashboard
Current:
- No fresh request is made to
/ - Router reuses the cached redirect from initial prefetch
- Middleware is never re-evaluated
- User stuck on
/processpage
The router appears to cache the redirect response from the prefetch and reuses it on router.push() without checking if the state has changed.
Provide environment information
Relevant Packages:
- next: 15.5.8 (also tested on 15.5.9, bug reproduces on both)
- react: 19.1.0
- react-dom: 19.1.0
- typescript: ^5
Which area(s) are affected? (Select all that apply)
Linking and Navigating, Middleware
Which stage(s) are affected? (Select all that apply)
Vercel (Deployed), Other (Deployed), next build (local)
Additional context
Workarounds:
-
Disable prefetch (works reliably):
<Link href="/" prefetch={false}>Dashboard</Link> -
Hard navigation (works reliably):
window.location.href = '/'; -
Router refresh before push (unreliable):
router.refresh();
await new Promise(resolve => setTimeout(resolve, 100));
router.push('/');
Impact:
This affects any route where NextResponse.redirect() was previously returned by middleware. Once the redirect is cached from a prefetch, subsequent router.push() calls to that route will reuse the cached redirect without re-evaluating middleware logic, even if the application state has changed.
Common scenarios affected:
- Onboarding/signup flows with step-based redirects
- Authentication state transitions
- Feature flag-based routing