Skip to content

Commit 6a974ad

Browse files
ztannerijjk
andauthored
[backport v14]: fix router handling when setting a location response header (#82588) (#82754)
Backports: - #82588 Co-authored-by: JJ Kasper <[email protected]>
1 parent 55f7662 commit 6a974ad

File tree

3 files changed

+67
-10
lines changed

3 files changed

+67
-10
lines changed

packages/next/src/server/lib/router-utils/resolve-routes.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import { formatHostname } from '../format-hostname'
1818
import { toNodeOutgoingHttpHeaders } from '../../web/utils'
1919
import { isAbortError } from '../../pipe-readable'
2020
import { getHostname } from '../../../shared/lib/get-hostname'
21-
import { getRedirectStatus } from '../../../lib/redirect-status'
21+
import {
22+
allowedStatusCodes,
23+
getRedirectStatus,
24+
} from '../../../lib/redirect-status'
2225
import { normalizeRepeatedSlashes } from '../../../shared/lib/utils'
2326
import { relativizeURL } from '../../../shared/lib/router/utils/relativize-url'
2427
import { addPathPrefix } from '../../../shared/lib/router/utils/add-path-prefix'
@@ -640,15 +643,35 @@ export function getResolveRoutes(
640643

641644
if (middlewareHeaders['location']) {
642645
const value = middlewareHeaders['location'] as string
643-
const rel = relativizeURL(value, initUrl)
644-
resHeaders['location'] = rel
645-
parsedUrl = url.parse(rel, true)
646+
// Only process Location header as a redirect if it has a proper redirect status
647+
// This prevents a Location header with non-redirect status from being treated as a redirect
648+
const isRedirectStatus = allowedStatusCodes.has(
649+
middlewareRes.status
650+
)
646651

647-
return {
648-
parsedUrl,
649-
resHeaders,
650-
finished: true,
651-
statusCode: middlewareRes.status,
652+
if (isRedirectStatus) {
653+
// Process as redirect: update parsedUrl and convert to relative URL
654+
const rel = relativizeURL(value, initUrl)
655+
resHeaders['location'] = rel
656+
parsedUrl = url.parse(rel, true)
657+
658+
return {
659+
parsedUrl,
660+
resHeaders,
661+
finished: true,
662+
statusCode: middlewareRes.status,
663+
}
664+
} else {
665+
// Not a redirect: just pass through the Location header
666+
resHeaders['location'] = value
667+
668+
return {
669+
parsedUrl,
670+
resHeaders,
671+
finished: true,
672+
bodyStream,
673+
statusCode: middlewareRes.status,
674+
}
652675
}
653676
}
654677

test/e2e/app-dir/app-middleware/app-middleware.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ createNextDescribe(
1111
files: __dirname,
1212
skipDeployment: true,
1313
},
14-
({ next }) => {
14+
({ next, isNextDeploy }) => {
1515
it('should filter correctly after middleware rewrite', async () => {
1616
const browser = await next.browser('/start')
1717

@@ -240,6 +240,29 @@ createNextDescribe(
240240

241241
await browser.deleteCookies()
242242
})
243+
244+
// TODO: This consistently 404s on Vercel deployments. It technically
245+
// doesn't repro the bug we're trying to fix but we need to figure out
246+
// why the handling is different.
247+
if (!isNextDeploy) {
248+
it('should not incorrectly treat a Location header as a rewrite', async () => {
249+
const res = await next.fetch('/test-location-header')
250+
251+
// Should get status 200 (not a redirect status)
252+
expect(res.status).toBe(200)
253+
254+
// Should get the JSON response associated with the route,
255+
// and not follow the redirect
256+
const json = await res.json()
257+
expect(json).toEqual({ foo: 'bar' })
258+
259+
// Ensure the provided location is still on the response
260+
const locationHeader = res.headers.get('location')
261+
expect(locationHeader).toBe(
262+
'https://next-data-api-endpoint.vercel.app/api/random'
263+
)
264+
})
265+
}
243266
}
244267
)
245268

test/e2e/app-dir/app-middleware/middleware.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ export async function middleware(request) {
6969
return res
7070
}
7171

72+
if (request.nextUrl.pathname === '/test-location-header') {
73+
return NextResponse.json(
74+
{ foo: 'bar' },
75+
{
76+
headers: {
77+
location: 'https://next-data-api-endpoint.vercel.app/api/random',
78+
},
79+
}
80+
)
81+
}
82+
7283
return NextResponse.next({
7384
request: {
7485
headers: headersFromRequest,

0 commit comments

Comments
 (0)