From be1397a5a551ea8affee3ae4ccc1d524baa03d66 Mon Sep 17 00:00:00 2001 From: Jim Fisher Date: Wed, 2 Feb 2022 02:57:04 +0000 Subject: [PATCH] Bug fix: dynamic page should not be interpreted as predefined page (#33808) Fixes: https://github.com/vercel/next.js/issues/17096 Fixes: https://github.com/vercel/next.js/issues/23824 Closes: https://github.com/vercel/next.js/pull/33765 ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added in https://github.com/vercel/next.js/pull/33765 - [x] Errors have helpful link attached (N/A) Co-authored-by: Tim Neutkens <6324199+timneutkens@users.noreply.github.com> Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- packages/next/server/base-server.ts | 22 ++++--- .../dynamic-route-interpolation/index.test.ts | 63 +++++++++++++++++++ 2 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 test/e2e/dynamic-route-interpolation/index.test.ts diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index e327ba718af0e..0d28b29ae6be5 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -881,7 +881,7 @@ export default abstract class Server { ): Promise { let page = pathname let params: Params | false = false - let pageFound = await this.hasPage(page) + let pageFound = !isDynamicRoute(page) && (await this.hasPage(page)) if (!pageFound && this.dynamicRoutes) { for (const dynamicRoute of this.dynamicRoutes) { @@ -1536,15 +1536,19 @@ export default abstract class Server { delete query._nextBubbleNoFallback try { - const result = await this.findPageComponents(pathname, query) - if (result) { - try { - return await this.renderToResponseWithComponents(ctx, result) - } catch (err) { - const isNoFallbackError = err instanceof NoFallbackError + // Ensure a request to the URL /accounts/[id] will be treated as a dynamic + // route correctly and not loaded immediately without parsing params. + if (!isDynamicRoute(pathname)) { + const result = await this.findPageComponents(pathname, query) + if (result) { + try { + return await this.renderToResponseWithComponents(ctx, result) + } catch (err) { + const isNoFallbackError = err instanceof NoFallbackError - if (!isNoFallbackError || (isNoFallbackError && bubbleNoFallback)) { - throw err + if (!isNoFallbackError || (isNoFallbackError && bubbleNoFallback)) { + throw err + } } } } diff --git a/test/e2e/dynamic-route-interpolation/index.test.ts b/test/e2e/dynamic-route-interpolation/index.test.ts new file mode 100644 index 0000000000000..441a5a5ac3bb8 --- /dev/null +++ b/test/e2e/dynamic-route-interpolation/index.test.ts @@ -0,0 +1,63 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { renderViaHTTP } from 'next-test-utils' +import cheerio from 'cheerio' + +describe('Dynamic Route Interpolation', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/blog/[slug].js': ` + export function getServerSideProps({ params }) { + return { props: { slug: params.slug } } + } + + export default function Page(props) { + return

{props.slug}

+ } + + `, + + 'pages/api/dynamic/[slug].js': ` + export default function Page(req, res) { + const { slug } = req.query + res.end('slug: ' + slug) + } + + `, + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('should work', async () => { + const html = await renderViaHTTP(next.url, '/blog/a') + const $ = cheerio.load(html) + expect($('#slug').text()).toBe('a') + }) + + it('should work with parameter itself', async () => { + const html = await renderViaHTTP(next.url, '/blog/[slug]') + const $ = cheerio.load(html) + expect($('#slug').text()).toBe('[slug]') + }) + + it('should work with brackets', async () => { + const html = await renderViaHTTP(next.url, '/blog/[abc]') + const $ = cheerio.load(html) + expect($('#slug').text()).toBe('[abc]') + }) + + it('should work with parameter itself in API routes', async () => { + const text = await renderViaHTTP(next.url, '/api/dynamic/[slug]') + expect(text).toBe('slug: [slug]') + }) + + it('should work with brackets in API routes', async () => { + const text = await renderViaHTTP(next.url, '/api/dynamic/[abc]') + expect(text).toBe('slug: [abc]') + }) +})