Instant-navigation validation reports a deliberate `redirect()` from a layout as a validation failure
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/orcunbalcilar/nextjs-instant-validation-redirect-repro
To Reproduce
npm install && npm run dev- Request
/dashboardtwice —curl -s -o /dev/null http://localhost:3000/dashboard, then the same again.
The second request is the point: on the cold compile the message is suppressed (the devRenderDidError branch in getNavigationDisallowedDynamicReasons), so a single request looks like it does not reproduce.
/dashboard has a layout in the shape the Cache Components migration guide recommends — the request-bound read is kept out of the layout's top level, inside a <Suspense> boundary — and that shell redirects when a cookie is absent:
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<Suspense fallback={<p>loading shell…</p>}>
<Shell>{children}</Shell>
</Suspense>
)
}
async function Shell({ children }: { children: React.ReactNode }) {
const jar = await cookies()
if (!jar.get("seeded")) {
redirect("/elsewhere") // an ordinary gate, not an error
}
return <main>{children}</main>
}
Current vs. Expected behavior
Current. Every warm request to /dashboard logs:
Error: Route "/dashboard": Could not validate `instant` because the target segment was prevented
from rendering, likely due to the following error.
Error: An error occurred while attempting to validate instant UI. This error may be preventing the
validation from completing.
at DashboardLayout (app/dashboard/layout.tsx:13:5)
[cause]: Error: NEXT_REDIRECT
at Shell (app/dashboard/layout.tsx:23:13)
environmentName: 'Prefetch',
digest: 'NEXT_REDIRECT;replace;/elsewhere;307;'
Expected. A redirect() is deliberate control flow, not a render failure. I would expect validation to treat the route as "redirected, nothing to validate" and stay silent, the way catchError already declines to swallow the router sentinels.
Why this matters beyond the noise. While the redirect is thrown, validation for that request is inconclusive, so a genuine finding on the same route is not reported. Replace app/dashboard/page.tsx with a Server Component that does blocking IO:
import { connection } from "next/server"
export default async function DashboardPage() {
await connection()
return <p>dashboard</p>
}
then request /dashboard twice each way:
| Request | Logged |
|---|---|
curl -H 'Cookie: seeded=1' .../dashboard (redirect does not fire) |
Next.js encountered uncached data … → blocking-prerender-dynamic — correct |
curl .../dashboard (redirect fires) |
only Could not validate \instant` …`; the real finding is gone |
So the message can hide the diagnostics it exists to surface.
Neither documented opt-out applies:
| Attempt | Result |
|---|---|
export const instant = false on app/dashboard/layout.tsx |
no change — the reported line numbers shift by the added line, so the config is loaded, but the message stays |
export const instant = false on app/dashboard/page.tsx |
hard 500 — "instant" is a route segment config and can only be used when the segment is a Server Component module |
An interactive dashboard page is normally a Client Component, so the page-level opt-out is unavailable in exactly the case that needs it. #98666 reaches the same dead end from a different cause (next/root-params).
Where it comes from. Both call sites in packages/next/src/server/app-render/app-render.tsx (onError and onBrowserBailout) pass the thrown value straight to trackThrownErrorInNavigation, which wraps whatever it receives when there is no validation boundary on the component stack. Neither filters the router sentinels. isNextRouterError already exists for this, and its own doc comment describes the case precisely: "These errors are thrown by user code to perform navigation operations and interrupt the React render."
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.6.0: Fri Jul 31 19:18:49 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6000
Available memory (MB): 16384
Available CPU cores: 10
Binaries:
Node: 26.8.2
npm: 11.19.1
Yarn: 1.22.22
pnpm: 12.3.4
Relevant Packages:
next: 16.4.0-canary.32 // Latest available version is detected (16.4.0-canary.32).
eslint-config-next: N/A
react: 19.3.0
react-dom: 19.3.0
typescript: 7.0.2
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
cacheComponents, Redirects, Error Overlay
Which stage(s) are affected? (Select all that apply)
next dev (local)
Additional context
First hit on 16.3.5; reproduced unchanged on 16.4.0-canary.32. Found in a real app where the workspace layout gates on a session and a saved-project seed, so it fired on every cold entry to the dashboard. The reproduction above was reduced from it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the issue with the linked app by requesting /dashboard twice, then inspect both trackThrownErrorInNavigation call sites in packages/next/src/server/app-render/app-render.tsx and the devRenderDidError branch in getNavigationDisallowedDynamicReasons. Compare the thrown value with isNextRouterError, and verify that deliberate redirects no longer produce instant-validation errors while the blocking-prerender-dynamic finding still appears when the cookie is present.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, next.js, react
- Domain
- backend, testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100