diff --git a/errors/invalid-multi-match.md b/errors/invalid-multi-match.md new file mode 100644 index 000000000000..cf9596b23d77 --- /dev/null +++ b/errors/invalid-multi-match.md @@ -0,0 +1,27 @@ +# Invalid Multi-match + +#### Why This Error Occurred + +In one of your custom-routes you specified a multi-match `/:path*` and used it in your `destination` without adding the `*` in your `destination` e.g. `destination: '/another/:path'` + +#### Possible Ways to Fix It + +Add `*` to your usage of the multi-match param in your `destination`. + +**Before** + +```js +{ + source: '/:path*', + destination: '/another/:path' +} +``` + +**After** + +```js +{ + source: '/:path*', + destination: '/another/:path*' +} +``` diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index 104d00957c74..02e8cbf0ed45 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -412,7 +412,20 @@ export default class Server { name: `${route.type} ${route.source} route`, fn: async (_req, res, params, _parsedUrl) => { let destinationCompiler = pathToRegexp.compile(route.destination) - let newUrl = destinationCompiler(params) + let newUrl + + try { + newUrl = destinationCompiler(params) + } catch (err) { + if ( + err.message.match(/Expected .*? to not repeat, but got array/) + ) { + throw new Error( + `To use a multi-match in the destination you must add \`*\` at the end of the param name to signify it should repeat. https://err.sh/zeit/next.js/invalid-multi-match` + ) + } + throw err + } if (route.type === 'redirect') { res.setHeader('Location', newUrl) diff --git a/test/integration/invalid-multi-match/next.config.js b/test/integration/invalid-multi-match/next.config.js new file mode 100644 index 000000000000..46cb98f27cef --- /dev/null +++ b/test/integration/invalid-multi-match/next.config.js @@ -0,0 +1,12 @@ +module.exports = { + experimental: { + rewrites() { + return [ + { + source: '/:hello*', + destination: '/:hello', + }, + ] + }, + }, +} diff --git a/test/integration/invalid-multi-match/pages/hello.js b/test/integration/invalid-multi-match/pages/hello.js new file mode 100644 index 000000000000..1b69a605b6fe --- /dev/null +++ b/test/integration/invalid-multi-match/pages/hello.js @@ -0,0 +1,15 @@ +import { useRouter } from 'next/router' + +console.log('hello from hello.js') + +const Page = () => { + const { query } = useRouter() + return ( + <> +

hello world

+ {JSON.stringify(query)} + + ) +} + +export default Page diff --git a/test/integration/invalid-multi-match/test/index.test.js b/test/integration/invalid-multi-match/test/index.test.js new file mode 100644 index 000000000000..7bfddbefbb7f --- /dev/null +++ b/test/integration/invalid-multi-match/test/index.test.js @@ -0,0 +1,57 @@ +/* eslint-env jest */ +/* global jasmine */ +import { join } from 'path' +import { + launchApp, + killApp, + findPort, + nextBuild, + nextStart, + renderViaHTTP, +} from 'next-test-utils' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2 + +let appDir = join(__dirname, '..') +let stderr = '' +let appPort +let app + +const runTests = () => { + it('should show error for invalid mulit-match', async () => { + await renderViaHTTP(appPort, '/hello') + expect(stderr).toContain( + 'To use a multi-match in the destination you must add' + ) + expect(stderr).toContain('https://err.sh/zeit/next.js/invalid-multi-match') + }) +} + +describe('Custom routes', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort, { + onStderr: msg => { + stderr += msg + }, + }) + }) + afterAll(() => killApp(app)) + runTests(true) + }) + + describe('production mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort, { + onStderr: msg => { + stderr += msg + }, + }) + }) + afterAll(() => killApp(app)) + runTests() + }) +})