diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts index 526d4b5d615d7..99c6eb9aaf528 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts @@ -363,7 +363,11 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) { } const statusCode = getRedirectStatus(redirect) - if (basePath && redirect.basePath !== false) { + if ( + basePath && + redirect.basePath !== false && + redirect.destination.startsWith('/') + ) { redirect.destination = `${basePath}${redirect.destination}` } diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index ca02ba875a784..f009aef2c0f4c 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -1482,7 +1482,11 @@ export default class Server { const statusCode = getRedirectStatus(redirect) const { basePath } = this.nextConfig - if (basePath && redirect.basePath !== false) { + if ( + basePath && + redirect.basePath !== false && + redirect.destination.startsWith('/') + ) { redirect.destination = `${basePath}${redirect.destination}` } diff --git a/test/integration/gssp-redirect-base-path/pages/gsp-blog/[post].js b/test/integration/gssp-redirect-base-path/pages/gsp-blog/[post].js index 8ce2202218ae3..70e3415dda472 100644 --- a/test/integration/gssp-redirect-base-path/pages/gsp-blog/[post].js +++ b/test/integration/gssp-redirect-base-path/pages/gsp-blog/[post].js @@ -28,7 +28,9 @@ export const getStaticProps = ({ params }) => { if (params.post.startsWith('redir')) { let destination = '/404' - if (params.post.includes('dest-')) { + if (params.post.includes('dest-external')) { + destination = 'https://example.com' + } else if (params.post.includes('dest-')) { destination = params.post.split('dest-').pop().replace(/_/g, '/') } diff --git a/test/integration/gssp-redirect-base-path/pages/gssp-blog/[post].js b/test/integration/gssp-redirect-base-path/pages/gssp-blog/[post].js index bd3d1f82ab1be..57fda28619e9e 100644 --- a/test/integration/gssp-redirect-base-path/pages/gssp-blog/[post].js +++ b/test/integration/gssp-redirect-base-path/pages/gssp-blog/[post].js @@ -22,7 +22,9 @@ export const getServerSideProps = ({ params }) => { if (params.post.startsWith('redir')) { let destination = '/404' - if (params.post.includes('dest-')) { + if (params.post.includes('dest-external')) { + destination = 'https://example.com' + } else if (params.post.includes('dest-')) { destination = params.post.split('dest-').pop().replace(/_/g, '/') } diff --git a/test/integration/gssp-redirect-base-path/test/index.test.js b/test/integration/gssp-redirect-base-path/test/index.test.js index f868aa3c929ad..cf38969fda73a 100644 --- a/test/integration/gssp-redirect-base-path/test/index.test.js +++ b/test/integration/gssp-redirect-base-path/test/index.test.js @@ -207,6 +207,54 @@ const runTests = (isDev) => { expect(pathname).toBe('/missing') }) + it('should apply redirect when fallback GSP page is visited directly (external domain)', async () => { + const browser = await webdriver( + appPort, + `${basePath}/gsp-blog/redirect-dest-external`, + true, + true + ) + + await check( + () => browser.eval(() => document.location.hostname), + 'example.com' + ) + + const initialHref = await browser.eval(() => window.initialHref) + expect(initialHref).toBe(null) + }) + + it('should apply redirect when fallback GSSP page is visited directly (external domain)', async () => { + const browser = await webdriver( + appPort, + `${basePath}/gssp-blog/redirect-dest-external`, + true, + true + ) + + await check( + () => browser.eval(() => document.location.hostname), + 'example.com' + ) + + const initialHref = await browser.eval(() => window.initialHref) + expect(initialHref).toBe(null) + + const res = await fetchViaHTTP( + appPort, + `${basePath}/gssp-blog/redirect-dest-external`, + undefined, + { + redirect: 'manual', + } + ) + expect(res.status).toBe(307) + + const parsed = url.parse(res.headers.get('location')) + expect(parsed.hostname).toBe('example.com') + expect(parsed.pathname).toBe('/') + }) + it('should apply redirect when GSSP page is navigated to client-side (internal dynamic)', async () => { const browser = await webdriver( appPort, diff --git a/test/integration/gssp-redirect/pages/gsp-blog/[post].js b/test/integration/gssp-redirect/pages/gsp-blog/[post].js index c571c57f23ea5..4701d1b925c76 100644 --- a/test/integration/gssp-redirect/pages/gsp-blog/[post].js +++ b/test/integration/gssp-redirect/pages/gsp-blog/[post].js @@ -28,7 +28,9 @@ export const getStaticProps = ({ params }) => { if (params.post.startsWith('redir')) { let destination = '/404' - if (params.post.includes('dest-')) { + if (params.post.includes('dest-external')) { + destination = 'https://example.com' + } else if (params.post.includes('dest-')) { destination = params.post.split('dest-').pop().replace(/_/g, '/') } diff --git a/test/integration/gssp-redirect/pages/gssp-blog/[post].js b/test/integration/gssp-redirect/pages/gssp-blog/[post].js index c1555f1ce4bc1..73921de9334eb 100644 --- a/test/integration/gssp-redirect/pages/gssp-blog/[post].js +++ b/test/integration/gssp-redirect/pages/gssp-blog/[post].js @@ -22,7 +22,9 @@ export const getServerSideProps = ({ params }) => { if (params.post.startsWith('redir')) { let destination = '/404' - if (params.post.includes('dest-')) { + if (params.post.includes('dest-external')) { + destination = 'https://example.com' + } else if (params.post.includes('dest-')) { destination = params.post.split('dest-').pop().replace(/_/g, '/') } diff --git a/test/integration/gssp-redirect/test/index.test.js b/test/integration/gssp-redirect/test/index.test.js index 441c4107e849f..79d1d8604b0ca 100644 --- a/test/integration/gssp-redirect/test/index.test.js +++ b/test/integration/gssp-redirect/test/index.test.js @@ -188,6 +188,54 @@ const runTests = (isDev) => { expect(pathname).toBe('/missing') }) + it('should apply redirect when fallback GSP page is visited directly (external domain)', async () => { + const browser = await webdriver( + appPort, + '/gsp-blog/redirect-dest-external', + true, + true + ) + + await check( + () => browser.eval(() => document.location.hostname), + 'example.com' + ) + + const initialHref = await browser.eval(() => window.initialHref) + expect(initialHref).toBe(null) + }) + + it('should apply redirect when fallback GSSP page is visited directly (external domain)', async () => { + const browser = await webdriver( + appPort, + '/gssp-blog/redirect-dest-external', + true, + true + ) + + await check( + () => browser.eval(() => document.location.hostname), + 'example.com' + ) + + const initialHref = await browser.eval(() => window.initialHref) + expect(initialHref).toBe(null) + + const res = await fetchViaHTTP( + appPort, + '/gssp-blog/redirect-dest-external', + undefined, + { + redirect: 'manual', + } + ) + expect(res.status).toBe(307) + + const parsed = url.parse(res.headers.get('location')) + expect(parsed.hostname).toBe('example.com') + expect(parsed.pathname).toBe('/') + }) + it('should apply redirect when GSSP page is navigated to client-side (internal dynamic)', async () => { const browser = await webdriver( appPort,