Repository: vercel/next.js
Pull request: Fix validateRSCRequestHeaders incorrect redirect

Found during dogfooding on Vercel that sometimes the RSC validation fails incorrectly. The root cause was that headers like `Next-Router-Prefetch` were absent from the header and transferred to request meta. Eventually we should improve how we retrieve these fields consistently.

Changed files:
filename: packages/next/src/server/base-server.ts
patch: @@ -2013,9 +2013,18 @@ export default abstract class Server<
       isRSCRequest
     ) {
       const headers = req.headers
+
+      const isPrefetchRSCRequest =
+        headers[NEXT_ROUTER_PREFETCH_HEADER.toLowerCase()] ||
+        getRequestMeta(req, 'isPrefetchRSCRequest')
+
+      const segmentPrefetchRSCRequest =
+        headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER.toLowerCase()] ||
+        getRequestMeta(req, 'segmentPrefetchRSCRequest')
+
       const expectedHash = computeCacheBustingSearchParam(
-        headers[NEXT_ROUTER_PREFETCH_HEADER.toLowerCase()],
-        headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER.toLowerCase()],
+        isPrefetchRSCRequest ? '1' : '0',
+        segmentPrefetchRSCRequest,
         headers[NEXT_ROUTER_STATE_TREE_HEADER.toLowerCase()],
         headers[NEXT_URL.toLowerCase()]
       )

filename: packages/next/src/shared/lib/router/utils/cache-busting-search-param.ts
patch: @@ -1,13 +1,13 @@
 import { hexHash } from '../../hash'
 
 export function computeCacheBustingSearchParam(
-  prefetchHeader: string | string[] | undefined,
+  prefetchHeader: '1' | '0' | undefined,
   segmentPrefetchHeader: string | string[] | undefined,
   stateTreeHeader: string | string[] | undefined,
   nextUrlHeader: string | string[] | undefined
 ): string {
   if (
-    prefetchHeader === undefined &&
+    (prefetchHeader === undefined || prefetchHeader === '0') &&
     segmentPrefetchHeader === undefined &&
     stateTreeHeader === undefined &&
     nextUrlHeader === undefined

filename: test/e2e/app-dir/app-prefetch/prefetching.test.ts
patch: @@ -305,7 +305,7 @@ describe('app dir - prefetching', () => {
 
     const url = new URL('/prefetch-auto/justputit', 'http://localhost')
     const cacheBustingParam = computeCacheBustingSearchParam(
-      headers['Next-Router-Prefetch'],
+      headers['Next-Router-Prefetch'] ? '1' : '0',
       undefined,
       headers['Next-Router-State-Tree'],
       headers['Next-Url']

filename: test/e2e/app-dir/ppr-full/ppr-full.test.ts
patch: @@ -52,7 +52,7 @@ const addCacheBustingSearchParam = (
   headers: Record<string, string | string[] | undefined>
 ) => {
   const cacheKey = computeCacheBustingSearchParam(
-    headers['Next-Router-Prefetch'],
+    headers['Next-Router-Prefetch'] ? '1' : '0',
     headers['Next-Router-Segment-Prefetch'],
     headers['Next-Router-State-Tree'],
     headers['Next-URL']

filename: test/e2e/app-dir/rewrite-headers/rewrite-headers.test.ts
patch: @@ -418,7 +418,7 @@ describe('rewrite-headers', () => {
         // Add cache busting param for RSC requests
         if (headers.RSC === '1') {
           const cacheBustingParam = computeCacheBustingSearchParam(
-            headers['Next-Router-Prefetch'],
+            headers['Next-Router-Prefetch'] ? '1' : '0',
             undefined,
             headers['Next-Router-State-Tree'],
             undefined