From ff2d28c4ff0a2cc1ce408817f2714eebf1cf72c2 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 22 Nov 2019 19:23:01 -0600 Subject: [PATCH] Add err.sh for invalid multi-match usage (#9498) * Adds err.sh for multi-match error * Fix missing apostrophe * Update regex to catch other param names * Add test for multi-match error * Update test/integration/invalid-multi-match/test/index.test.js --- errors/invalid-multi-match.md | 27 +++++++++ .../next/next-server/server/next-server.ts | 15 ++++- .../invalid-multi-match/next.config.js | 12 ++++ .../invalid-multi-match/pages/hello.js | 15 +++++ .../invalid-multi-match/test/index.test.js | 57 +++++++++++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 errors/invalid-multi-match.md create mode 100644 test/integration/invalid-multi-match/next.config.js create mode 100644 test/integration/invalid-multi-match/pages/hello.js create mode 100644 test/integration/invalid-multi-match/test/index.test.js 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() + }) +})