From 0ddcedd06449abc0a1cfd27c928caedd779690f0 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 09:00:05 +0200 Subject: [PATCH 01/10] Add support for catchall with new router --- packages/next/server/app-render.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 0362a9fdffcc5..3b23351d24139 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -383,15 +383,22 @@ export async function renderToHTML( // TODO: use correct matching for dynamic routes to get segment param const segmentParam = segment.startsWith('[') && segment.endsWith(']') - ? segment.slice(1, -1) + ? segment.slice(segment.startsWith('[...') ? 4 : 1, -1) : null - if (!segmentParam || !pathParams[segmentParam]) { + if (!segmentParam || (!pathParams[segmentParam] && !query[segmentParam])) { return null } - // @ts-expect-error TODO: handle case where value is an array - return { param: segmentParam, value: pathParams[segmentParam] } + return { + param: segmentParam, + value: + // TODO: this should only read from `pathParams`. There's an inconsistency where `query` holds params currently which has to be fixed. + pathParams[segmentParam] ?? Array.isArray(query[segmentParam]) + ? // @ts-expect-error TODO: handle case where value is an array + query[segmentParam].join('/') + : query[segmentParam], + } } const createFlightRouterStateFromLoaderTree = ([ @@ -544,10 +551,9 @@ export async function renderToHTML( | GetServerSidePropsContext | getServerSidePropsContextPage = { headers, - // TODO: convert to NextCookies cookies, layoutSegments: segmentPath, - // TODO: change this to be URLSearchParams instead? + // TODO: Currently query holds params and pathname is not the actual pathname, it holds the dynamic parameter ...(isPage ? { query, pathname } : {}), ...(pageIsDynamic ? { params: currentParams } : undefined), ...(isPreview From 1672a77c7e384e2814c111e838e8c4af70c799d6 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 09:20:02 +0200 Subject: [PATCH 02/10] Add missing brackets --- packages/next/server/app-render.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 3b23351d24139..19c6d38481ce9 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -394,10 +394,11 @@ export async function renderToHTML( param: segmentParam, value: // TODO: this should only read from `pathParams`. There's an inconsistency where `query` holds params currently which has to be fixed. - pathParams[segmentParam] ?? Array.isArray(query[segmentParam]) + pathParams[segmentParam] ?? + (Array.isArray(query[segmentParam]) ? // @ts-expect-error TODO: handle case where value is an array query[segmentParam].join('/') - : query[segmentParam], + : query[segmentParam]), } } From 632acc55f7b83a0c2319678b09502db4d376f640 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 09:21:11 +0200 Subject: [PATCH 03/10] Update app-render.tsx --- packages/next/server/app-render.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 19c6d38481ce9..6aa9b4e8942cc 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -392,6 +392,7 @@ export async function renderToHTML( return { param: segmentParam, + // @ts-expect-error TODO: handle case where value is an array value: // TODO: this should only read from `pathParams`. There's an inconsistency where `query` holds params currently which has to be fixed. pathParams[segmentParam] ?? From 0e899d1f66c1f407932be643b2672ad765834b9a Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 11:04:47 +0200 Subject: [PATCH 04/10] Update base-server.ts --- packages/next/server/base-server.ts | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index f1fc51f5f6023..a096b4380f220 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -36,6 +36,7 @@ import { format as formatUrl, parse as parseUrl } from 'url' import { getRedirectStatus } from '../lib/load-custom-routes' import { NEXT_BUILTIN_DOCUMENT, + NEXT_CLIENT_SSR_ENTRY_SUFFIX, SERVERLESS_DIRECTORY, SERVER_DIRECTORY, STATIC_STATUS_PAGES, @@ -1026,33 +1027,14 @@ export default abstract class Server { const appPathRoutes: Record = {} Object.keys(this.appPathsManifest || {}).forEach((entry) => { + if (entry.endsWith(NEXT_CLIENT_SSR_ENTRY_SUFFIX)) { + return + } appPathRoutes[normalizeAppPath(entry) || '/'] = entry }) return appPathRoutes } - protected getAppPathLayouts(pathname: string): string[] { - const layoutPaths: string[] = [] - - if (this.appPathRoutes) { - const paths = Object.values(this.appPathRoutes) - const parts = pathname.split('/').filter(Boolean) - - for (let i = 1; i < parts.length; i++) { - const layoutPath = `/${parts.slice(0, i).join('/')}/layout` - - if (paths.includes(layoutPath)) { - layoutPaths.push(layoutPath) - } - } - - if (this.appPathRoutes['/layout']) { - layoutPaths.unshift('/layout') - } - } - return layoutPaths - } - protected async run( req: BaseNextRequest, res: BaseNextResponse, From 61bbdb15e6b0641b42882b41fa5bc9164075c492 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 11:05:03 +0200 Subject: [PATCH 05/10] Rename root -> app --- .../webpack/plugins/pages-manifest-plugin.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts index 7ec405f386679..86dbb50a51207 100644 --- a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts @@ -10,8 +10,8 @@ export type PagesManifest = { [page: string]: string } let edgeServerPages = {} let nodeServerPages = {} -let edgeServerRootPaths = {} -let nodeServerRootPaths = {} +let edgeServerAppPaths = {} +let nodeServerAppPaths = {} // This plugin creates a pages-manifest.json from page entrypoints. // This is used for mapping paths like `/` to `.next/server/static//pages/index.js` when doing SSR @@ -42,7 +42,7 @@ export default class PagesManifestPlugin implements webpack.Plugin { createAssets(compilation: any, assets: any) { const entrypoints = compilation.entrypoints const pages: PagesManifest = {} - const rootPaths: PagesManifest = {} + const appPaths: PagesManifest = {} for (const entrypoint of entrypoints.values()) { const pagePath = getRouteFromEntrypoint( @@ -78,7 +78,7 @@ export default class PagesManifestPlugin implements webpack.Plugin { file = normalizePathSep(file) if (entrypoint.name.startsWith('app/')) { - rootPaths[pagePath] = file + appPaths[pagePath] = file } else { pages[pagePath] = file } @@ -88,10 +88,10 @@ export default class PagesManifestPlugin implements webpack.Plugin { // we need to merge both pages to generate the full manifest. if (this.isEdgeRuntime) { edgeServerPages = pages - edgeServerRootPaths = rootPaths + edgeServerAppPaths = appPaths } else { nodeServerPages = pages - nodeServerRootPaths = rootPaths + nodeServerAppPaths = appPaths } assets[ @@ -113,8 +113,8 @@ export default class PagesManifestPlugin implements webpack.Plugin { ] = new sources.RawSource( JSON.stringify( { - ...edgeServerRootPaths, - ...nodeServerRootPaths, + ...edgeServerAppPaths, + ...nodeServerAppPaths, }, null, 2 From 28886a1f512ee2fda7baad73070c7a37bae713eb Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 11:05:22 +0200 Subject: [PATCH 06/10] Handle params being an array --- packages/next/server/app-render.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 0160ee179aad4..5787cd5a2ec52 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -394,10 +394,12 @@ export async function renderToHTML( return { param: segmentParam, - // @ts-expect-error TODO: handle case where value is an array value: // TODO: this should only read from `pathParams`. There's an inconsistency where `query` holds params currently which has to be fixed. - pathParams[segmentParam] ?? + (Array.isArray(pathParams[segmentParam]) + ? // @ts-expect-error TODO: handle case where value is an array + pathParams[segmentParam].join('/') + : pathParams[segmentParam]) ?? (Array.isArray(query[segmentParam]) ? // @ts-expect-error TODO: handle case where value is an array query[segmentParam].join('/') From 107c6e5e763c609eff54e0619c945199ad64bb16 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 11:05:33 +0200 Subject: [PATCH 07/10] Remove _root condition --- packages/next/server/require.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/next/server/require.ts b/packages/next/server/require.ts index e6cb5a0302640..77446af267675 100644 --- a/packages/next/server/require.ts +++ b/packages/next/server/require.ts @@ -28,9 +28,6 @@ export function getPagePath( let rootPathsManifest: undefined | PagesManifest if (appDirEnabled) { - if (page === '/_root') { - return join(serverBuildPath, 'root.js') - } rootPathsManifest = require(join(serverBuildPath, APP_PATHS_MANIFEST)) } const pagesManifest = require(join( From aaffd48e0f5e25281bdccf85e70cdce409d002b0 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 11:36:10 +0200 Subject: [PATCH 08/10] Add support for optional catch all routes --- packages/next/server/app-render.tsx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 5787cd5a2ec52..e090c9ac332cc 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -299,6 +299,22 @@ export type ChildProp = { segment: Segment } +function getSegmentParam(segment: string): string | null { + if (segment.startsWith('[[...') && segment.endsWith(']]')) { + return segment.slice(5, -2) + } + + if (segment.startsWith('[...') && segment.endsWith(']')) { + return segment.slice(4, -1) + } + + if (segment.startsWith('[') && segment.endsWith(']')) { + return segment.slice(1, -1) + } + + return null +} + export async function renderToHTML( req: IncomingMessage, res: ServerResponse, @@ -383,11 +399,7 @@ export async function renderToHTML( segment: string ): { param: string; value: string } | null => { // TODO: use correct matching for dynamic routes to get segment param - const segmentParam = - segment.startsWith('[') && segment.endsWith(']') - ? segment.slice(segment.startsWith('[...') ? 4 : 1, -1) - : null - + const segmentParam = getSegmentParam(segment) if (!segmentParam || (!pathParams[segmentParam] && !query[segmentParam])) { return null } From bc4530921239b54308ef0d810a56af3853d72044 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 11:48:17 +0200 Subject: [PATCH 09/10] Handle empty optional catchall --- packages/next/server/app-render.tsx | 49 ++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index e090c9ac332cc..75d39831bb312 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -299,17 +299,29 @@ export type ChildProp = { segment: Segment } -function getSegmentParam(segment: string): string | null { +function getSegmentParam(segment: string): { + param: string + type: 'catchall' | 'optional-catchall' | 'dynamic' +} | null { if (segment.startsWith('[[...') && segment.endsWith(']]')) { - return segment.slice(5, -2) + return { + type: 'optional-catchall', + param: segment.slice(5, -2), + } } if (segment.startsWith('[...') && segment.endsWith(']')) { - return segment.slice(4, -1) + return { + type: 'catchall', + param: segment.slice(4, -1), + } } if (segment.startsWith('[') && segment.endsWith(']')) { - return segment.slice(1, -1) + return { + type: 'dynamic', + param: segment.slice(1, -1), + } } return null @@ -400,22 +412,35 @@ export async function renderToHTML( ): { param: string; value: string } | null => { // TODO: use correct matching for dynamic routes to get segment param const segmentParam = getSegmentParam(segment) - if (!segmentParam || (!pathParams[segmentParam] && !query[segmentParam])) { + if (!segmentParam) { + return null + } + + const key = segmentParam.param + const type = segmentParam.type + + if (!segmentParam || (!pathParams[key] && !query[key])) { + if (type === 'optional-catchall') { + return { + param: key, + value: '', + } + } return null } return { - param: segmentParam, + param: key, value: // TODO: this should only read from `pathParams`. There's an inconsistency where `query` holds params currently which has to be fixed. - (Array.isArray(pathParams[segmentParam]) + (Array.isArray(pathParams[key]) ? // @ts-expect-error TODO: handle case where value is an array - pathParams[segmentParam].join('/') - : pathParams[segmentParam]) ?? - (Array.isArray(query[segmentParam]) + pathParams[key].join('/') + : pathParams[key]) ?? + (Array.isArray(query[key]) ? // @ts-expect-error TODO: handle case where value is an array - query[segmentParam].join('/') - : query[segmentParam]), + query[key].join('/') + : query[key]), } } From 4c81c50eab1112fb8e48048502e3a9c26e1c0233 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 8 Jul 2022 11:59:02 +0200 Subject: [PATCH 10/10] Simplify --- packages/next/server/app-render.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 75d39831bb312..1e632dd87b265 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -417,10 +417,9 @@ export async function renderToHTML( } const key = segmentParam.param - const type = segmentParam.type - if (!segmentParam || (!pathParams[key] && !query[key])) { - if (type === 'optional-catchall') { + if (!pathParams[key] && !query[key]) { + if (segmentParam.type === 'optional-catchall') { return { param: key, value: '',