From 2a571e29c05ff885c8efba97fa11516bdebb27e8 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 8 Sep 2022 18:49:09 +0200 Subject: [PATCH 1/5] Fix page url for edge routes in app dir --- packages/next/server/next-server.ts | 13 +++++++------ test/e2e/app-dir/rsc-basic.test.ts | 14 ++++++++++++++ .../rsc-basic/app/edge/dynamic/[id]/page.server.js | 7 +++++++ .../rsc-basic/app/edge/dynamic/page.server.js | 7 +++++++ 4 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js create mode 100644 test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 27316dd1fd3..a5eab6292be 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -763,7 +763,6 @@ export default class NextNodeServer extends BaseServer { params, page, appPaths: null, - isAppPath: false, }) if (handledAsEdgeFunction) { @@ -913,7 +912,6 @@ export default class NextNodeServer extends BaseServer { params: ctx.renderOpts.params, page, appPaths, - isAppPath, }) return null } @@ -2031,7 +2029,6 @@ export default class NextNodeServer extends BaseServer { params: Params | undefined page: string appPaths: string[] | null - isAppPath: boolean onWarning?: (warning: Error) => void }): Promise { let middlewareInfo: ReturnType | undefined @@ -2047,12 +2044,16 @@ export default class NextNodeServer extends BaseServer { return null } + // Erase extra queries + const locale = params.query.__nextLocale + delete params.query.__nextLocale + delete params.query.__nextDefaultLocale + // For middleware to "fetch" we must always provide an absolute URL const isDataReq = !!params.query.__nextDataReq const query = urlQueryToSearchParams(params.query).toString() - const locale = params.query.__nextLocale - // Use original pathname (without `/page`) instead of appPath for url - let normalizedPathname = params.page + + let normalizedPathname = params.req.url if (isDataReq) { params.req.headers['x-nextjs-data'] = '1' diff --git a/test/e2e/app-dir/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic.test.ts index c9996fe5ec0..0debb681839 100644 --- a/test/e2e/app-dir/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic.test.ts @@ -340,6 +340,20 @@ describe('app dir - react server components', () => { expect(head).toMatch(/{color:(\s*)blue;?}/) }) + it('should stick to the url without trailing /page suffix', async () => { + const browser = await webdriver(next.url, '/edge/dynamic') + const indexUrl = await browser.url() + + await browser.loadPage(`${next.url}/edge/dynamic/123`, { + disableCache: false, + beforePageLoad: null, + }) + + const dynamicRouteUrl = await browser.url() + expect(indexUrl).toBe(`${next.url}/edge/dynamic`) + expect(dynamicRouteUrl).toBe(`${next.url}/edge/dynamic/123`) + }) + it('should support streaming for flight response', async () => { await fetchViaHTTP(next.url, '/?__flight__=1').then(async (response) => { const result = await resolveStreamResponse(response) diff --git a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js new file mode 100644 index 00000000000..648a07075ac --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js @@ -0,0 +1,7 @@ +export default function page() { + return 'dynamic route [id] page' +} + +export const runtime = { + runtime: 'experimental-edge', +} diff --git a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js new file mode 100644 index 00000000000..62515b2d52e --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js @@ -0,0 +1,7 @@ +export default function page() { + return 'dynamic route index page' +} + +export const runtime = { + runtime: 'experimental-edge', +} From 1c0792927b873dd41b4df959edc789a05544af14 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 8 Sep 2022 20:51:02 +0200 Subject: [PATCH 2/5] read pathname from parsed url --- packages/next/server/next-server.ts | 22 ++++++++----------- .../shared/lib/router/utils/querystring.ts | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index a5eab6292be..87ca0af13d5 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -2033,7 +2033,8 @@ export default class NextNodeServer extends BaseServer { }): Promise { let middlewareInfo: ReturnType | undefined - const page = params.page + const { query, page, req } = params + const { pathname } = parseUrl(req.url) await this.ensureEdgeFunction({ page, appPaths: params.appPaths }) middlewareInfo = this.getEdgeFunctionInfo({ page, @@ -2044,34 +2045,29 @@ export default class NextNodeServer extends BaseServer { return null } - // Erase extra queries - const locale = params.query.__nextLocale - delete params.query.__nextLocale - delete params.query.__nextDefaultLocale - // For middleware to "fetch" we must always provide an absolute URL - const isDataReq = !!params.query.__nextDataReq - const query = urlQueryToSearchParams(params.query).toString() - - let normalizedPathname = params.req.url + const locale = query.__nextLocale + const isDataReq = !!query.__nextDataReq + const queryString = urlQueryToSearchParams(query).toString() if (isDataReq) { params.req.headers['x-nextjs-data'] = '1' } + let normalizedPathname = params.page if (isDynamicRoute(normalizedPathname)) { const routeRegex = getNamedRouteRegex(params.page) normalizedPathname = interpolateDynamicPath( params.page, - Object.assign({}, params.params, params.query), + Object.assign({}, params.params, query), routeRegex ) } const url = `${getRequestMeta(params.req, '_protocol')}://${ this.hostname - }:${this.port}${locale ? `/${locale}` : ''}${normalizedPathname}${ - query ? `?${query}` : '' + }:${this.port}${locale ? `/${locale}` : ''}${pathname}${ + queryString ? `?${queryString}` : '' }` if (!url.startsWith('http')) { diff --git a/packages/next/shared/lib/router/utils/querystring.ts b/packages/next/shared/lib/router/utils/querystring.ts index 3b9fa9f2068..aee7632e412 100644 --- a/packages/next/shared/lib/router/utils/querystring.ts +++ b/packages/next/shared/lib/router/utils/querystring.ts @@ -35,7 +35,7 @@ export function urlQueryToSearchParams( Object.entries(urlQuery).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach((item) => result.append(key, stringifyUrlQueryParam(item))) - } else { + } else if (value !== undefined) { result.set(key, stringifyUrlQueryParam(value)) } }) From 006366603c4af4a764f96e86f943116b123b4604 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 8 Sep 2022 21:23:31 +0200 Subject: [PATCH 3/5] preserve undefined query --- packages/next/shared/lib/router/utils/querystring.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/shared/lib/router/utils/querystring.ts b/packages/next/shared/lib/router/utils/querystring.ts index aee7632e412..3b9fa9f2068 100644 --- a/packages/next/shared/lib/router/utils/querystring.ts +++ b/packages/next/shared/lib/router/utils/querystring.ts @@ -35,7 +35,7 @@ export function urlQueryToSearchParams( Object.entries(urlQuery).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach((item) => result.append(key, stringifyUrlQueryParam(item))) - } else if (value !== undefined) { + } else { result.set(key, stringifyUrlQueryParam(value)) } }) From b45e0e4e8f1ea8a67c54fd4d4f60820ef254cd25 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 8 Sep 2022 23:25:59 +0200 Subject: [PATCH 4/5] use normalized page to interop --- packages/next/server/next-server.ts | 14 +++++++------- .../rsc-basic/app/edge/dynamic/[id]/page.server.js | 2 +- .../rsc-basic/app/edge/dynamic/page.server.js | 2 +- test/e2e/app-dir/rsc-basic/next.config.js | 10 ++++++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 87ca0af13d5..08bf80c1f68 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -27,7 +27,7 @@ import type { NextConfig } from './config-shared' import type { DynamicRoutes, PageChecker } from './router' import fs from 'fs' -import { join, relative, resolve, sep } from 'path' +import path, { join, relative, resolve, sep } from 'path' import { IncomingMessage, ServerResponse } from 'http' import { addRequestMeta, getRequestMeta } from './request-meta' import { isDynamicRoute } from '../shared/lib/router/utils' @@ -2033,8 +2033,8 @@ export default class NextNodeServer extends BaseServer { }): Promise { let middlewareInfo: ReturnType | undefined - const { query, page, req } = params - const { pathname } = parseUrl(req.url) + const { query, page } = params + await this.ensureEdgeFunction({ page, appPaths: params.appPaths }) middlewareInfo = this.getEdgeFunctionInfo({ page, @@ -2054,11 +2054,11 @@ export default class NextNodeServer extends BaseServer { params.req.headers['x-nextjs-data'] = '1' } - let normalizedPathname = params.page + let normalizedPathname = normalizeAppPath(page) if (isDynamicRoute(normalizedPathname)) { - const routeRegex = getNamedRouteRegex(params.page) + const routeRegex = getNamedRouteRegex(normalizedPathname) normalizedPathname = interpolateDynamicPath( - params.page, + normalizedPathname, Object.assign({}, params.params, query), routeRegex ) @@ -2066,7 +2066,7 @@ export default class NextNodeServer extends BaseServer { const url = `${getRequestMeta(params.req, '_protocol')}://${ this.hostname - }:${this.port}${locale ? `/${locale}` : ''}${pathname}${ + }:${this.port}${locale ? `/${locale}` : ''}${normalizedPathname}${ queryString ? `?${queryString}` : '' }` diff --git a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js index 648a07075ac..87bac2ff6be 100644 --- a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js +++ b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js @@ -2,6 +2,6 @@ export default function page() { return 'dynamic route [id] page' } -export const runtime = { +export const config = { runtime: 'experimental-edge', } diff --git a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js index 62515b2d52e..1b83f0120a6 100644 --- a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js +++ b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js @@ -2,6 +2,6 @@ export default function page() { return 'dynamic route index page' } -export const runtime = { +export const config = { runtime: 'experimental-edge', } diff --git a/test/e2e/app-dir/rsc-basic/next.config.js b/test/e2e/app-dir/rsc-basic/next.config.js index ca13fceaff3..eab44b6855a 100644 --- a/test/e2e/app-dir/rsc-basic/next.config.js +++ b/test/e2e/app-dir/rsc-basic/next.config.js @@ -7,4 +7,14 @@ module.exports = { appDir: true, serverComponents: true, }, + rewrites: async () => { + return { + afterFiles: [ + { + source: '/rewritten-to-edge-dynamic', + destination: '/edge/dynamic', + }, + ], + } + }, } From b98adf93e68d613874019b1deb8daae8b94e1740 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 8 Sep 2022 23:27:12 +0200 Subject: [PATCH 5/5] remove unsued --- packages/next/server/next-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 08bf80c1f68..f3cb0d914bb 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -27,7 +27,7 @@ import type { NextConfig } from './config-shared' import type { DynamicRoutes, PageChecker } from './router' import fs from 'fs' -import path, { join, relative, resolve, sep } from 'path' +import { join, relative, resolve, sep } from 'path' import { IncomingMessage, ServerResponse } from 'http' import { addRequestMeta, getRequestMeta } from './request-meta' import { isDynamicRoute } from '../shared/lib/router/utils'