diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 8782735790a5..65876d9f2fdb 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -49,7 +49,7 @@ import { getExtension, serveStatic } from './serve-static' import { ParsedUrlQuery } from 'querystring' import { apiResolver } from './api-utils/node' import { RenderOpts, renderToHTML } from './render' -import { ParsedUrl } from '../shared/lib/router/utils/parse-url' +import { ParsedUrl, parseUrl } from '../shared/lib/router/utils/parse-url' import * as Log from '../build/output/log' import BaseServer, { @@ -1131,12 +1131,8 @@ export default class NextNodeServer extends BaseServer { const rewritePath = result.response.headers.get( 'x-middleware-rewrite' )! - const { newUrl, parsedDestination } = prepareDestination({ - appendParamsToQuery: false, - destination: rewritePath, - params: _params, - query: {}, - }) + const parsedDestination = parseUrl(rewritePath) + const newUrl = parsedDestination.pathname // TODO: remove after next minor version current `v12.0.9` this.warnIfQueryParametersWereDeleted( diff --git a/test/integration/middleware/core/pages/rewrites/[param].js b/test/integration/middleware/core/pages/rewrites/[param].js new file mode 100644 index 000000000000..d33f75dd2da6 --- /dev/null +++ b/test/integration/middleware/core/pages/rewrites/[param].js @@ -0,0 +1,12 @@ +export const getServerSideProps = ({ params, query }) => { + return { props: { params, query } } +} + +export default function Page({ params: { param }, query: { qp } }) { + return ( + <> +

{param}

+

{qp}

+ + ) +} diff --git a/test/integration/middleware/core/pages/rewrites/_middleware.js b/test/integration/middleware/core/pages/rewrites/_middleware.js index d9b83488318c..dcb61e1d176d 100644 --- a/test/integration/middleware/core/pages/rewrites/_middleware.js +++ b/test/integration/middleware/core/pages/rewrites/_middleware.js @@ -41,6 +41,16 @@ export async function middleware(request) { return NextResponse.rewrite(url) } + if (url.pathname === '/rewrites/rewrite-me-with-a-colon') { + url.pathname = '/rewrites/with:colon' + return NextResponse.rewrite(url) + } + + if (url.pathname === '/rewrites/colon:here') { + url.pathname = '/rewrites/no-colon-here' + return NextResponse.rewrite(url) + } + if (url.pathname === '/rewrites/rewrite-me-to-vercel') { return NextResponse.rewrite('https://vercel.com') } diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index 5f309b9dcbf7..b1ad4d164eec 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -359,6 +359,86 @@ function rewriteTests(log, locale = '') { expect($('.title').text()).toBe('About Page') }) + it(`${locale} support colons in path`, async () => { + const path = `${locale}/rewrites/not:param` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('not:param') + const browser = await webdriver(context.appPort, path) + try { + expect(await browser.eval(`window.location.pathname`)).toBe(path) + } finally { + await browser.close() + } + }) + + it(`${locale} can rewrite to path with colon`, async () => { + const path = `${locale}/rewrites/rewrite-me-with-a-colon` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('with:colon') + const browser = await webdriver(context.appPort, path) + try { + expect(await browser.eval(`window.location.pathname`)).toBe(path) + } finally { + await browser.close() + } + }) + + it(`${locale} can rewrite from path with colon`, async () => { + const path = `${locale}/rewrites/colon:here` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('no-colon-here') + const browser = await webdriver(context.appPort, path) + try { + expect(await browser.eval(`window.location.pathname`)).toBe(path) + } finally { + await browser.close() + } + }) + + it(`${locale} can rewrite from path with colon and retain query parameter`, async () => { + const path = `${locale}/rewrites/colon:here?qp=arg` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('no-colon-here') + expect($('#qp').text()).toBe('arg') + const browser = await webdriver(context.appPort, path) + try { + expect( + await browser.eval( + `window.location.href.replace(window.location.origin, '')` + ) + ).toBe(path) + } finally { + await browser.close() + } + }) + + it(`${locale} can rewrite to path with colon and retain query parameter`, async () => { + const path = `${locale}/rewrites/rewrite-me-with-a-colon?qp=arg` + const res = await fetchViaHTTP(context.appPort, path) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#props').text()).toBe('with:colon') + expect($('#qp').text()).toBe('arg') + const browser = await webdriver(context.appPort, path) + try { + expect( + await browser.eval( + `window.location.href.replace(window.location.origin, '')` + ) + ).toBe(path) + } finally { + await browser.close() + } + }) + it(`${locale} should rewrite when not using localhost`, async () => { const res = await fetchViaHTTP( `http://localtest.me:${context.appPort}`,