Skip to content

Commit 463d30e

Browse files
authored
Add handling for external redirects (vercel#9764)
1 parent dbc0853 commit 463d30e

File tree

4 files changed

+34
-3
lines changed

4 files changed

+34
-3
lines changed

packages/next/lib/check-custom-routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default function checkCustomRoutes(
4040
invalidParts.push('`destination` is missing')
4141
} else if (typeof route.destination !== 'string') {
4242
invalidParts.push('`destination` is not a string')
43-
} else if (!route.destination.startsWith('/')) {
43+
} else if (type === 'rewrite' && !route.destination.startsWith('/')) {
4444
invalidParts.push('`destination` does not start with /')
4545
}
4646

packages/next/next-server/server/next-server.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,10 @@ export default class Server {
404404
statusCode: route.statusCode,
405405
name: `${route.type} ${route.source} route`,
406406
fn: async (_req, res, params, _parsedUrl) => {
407-
let destinationCompiler = compilePathToRegex(route.destination)
407+
const parsedDestination = parseUrl(route.destination, true)
408+
let destinationCompiler = compilePathToRegex(
409+
`${parsedDestination.pathname!}${parsedDestination.hash || ''}`
410+
)
408411
let newUrl
409412

410413
try {
@@ -423,7 +426,15 @@ export default class Server {
423426
}
424427

425428
if (route.type === 'redirect') {
426-
res.setHeader('Location', newUrl)
429+
const parsedNewUrl = parseUrl(newUrl)
430+
res.setHeader(
431+
'Location',
432+
formatUrl({
433+
...parsedDestination,
434+
pathname: parsedNewUrl.pathname,
435+
hash: parsedNewUrl.hash,
436+
})
437+
)
427438
res.statusCode = route.statusCode || DEFAULT_REDIRECT_STATUS
428439
res.end()
429440
return {

test/integration/custom-routes/next.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ module.exports = {
9292
destination: '/',
9393
statusCode: 303,
9494
},
95+
{
96+
source: '/to-external',
97+
destination: 'https://google.com',
98+
},
9599
]
96100
},
97101
},

test/integration/custom-routes/test/index.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ const runTests = (isDev = false) => {
152152
expect(await getBrowserBodyText(browser)).toMatch(/Hello again/)
153153
})
154154

155+
it('should allow redirecting to external resource', async () => {
156+
const res = await fetchViaHTTP(appPort, '/to-external', undefined, {
157+
redirect: 'manual',
158+
})
159+
const location = res.headers.get('location')
160+
expect(res.status).toBe(307)
161+
expect(location).toBe('https://google.com/')
162+
})
163+
155164
if (!isDev) {
156165
it('should output routes-manifest successfully', async () => {
157166
const manifest = await fs.readJSON(
@@ -237,6 +246,13 @@ const runTests = (isDev = false) => {
237246
regex: normalizeRegEx('^\\/redir-chain3$'),
238247
regexKeys: [],
239248
},
249+
{
250+
destination: 'https://google.com',
251+
regex: normalizeRegEx('^\\/to-external$'),
252+
regexKeys: [],
253+
source: '/to-external',
254+
statusCode: 307,
255+
},
240256
],
241257
rewrites: [
242258
{

0 commit comments

Comments
 (0)