AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

NotFound() Returns HTTP 200 Instead Of 404 When Streaming Is Enabled By Root Loading.Tsx And Sibling Dynamic Route

When a route with dynamicParams:false calls notFound(), it returns 200 with not-found UI if a root loading.tsx and a sibling dynamicParams:true route exist. The 404 status is lost during streaming; the incremental cache stores a 200 response.

highConfidence 78%Next.jsAffected V16.3.4Affected V16.3.6Affected V16.4.0-Canary.50

Origin Analysis

Next.js App Router enables streaming when a root loading.tsx is present. For routes with dynamicParams:false, an unknown param should trigger a 404 before streaming; however, the presence of a sibling dynamicParams:true route causes the route to be rendered on demand as a streamed response. The HTTP status 200 is committed before the page component runs and throws notFound(), so the 404 status cannot be applied. The cache therefore stores a 200 entry.
1. Create app/brands/[brand]/page.tsx with dynamicParams=false and notFound() for unknown brand. 2. Add app/loading.tsx. 3. Add a sibling dynamic route app/[category]/[slug]/page.tsx with dynamicParams=true. 4. Build and start, curl /brands/does-not-exist -> returns 200 with not-found UI.

Fixing Code Block

Edge Case Audit

This only addresses the primary route with dynamicParams:false; routes with dynamicParams:true that throw notFound() under streaming may still return 200. Disabling streaming can increase time-to-first-byte for such routes. Rollback by reverting the patch. Upstream fix may need a more sophisticated solution (e.g., pre-rendering the segment or delaying status until initial render completes).

Ecosystem Topology