diff --git a/.github/workflows/rspack-nextjs-build-integration-tests.yml b/.github/workflows/rspack-nextjs-build-integration-tests.yml index 135f9f3605bcc4..673742d73eab20 100644 --- a/.github/workflows/rspack-nextjs-build-integration-tests.yml +++ b/.github/workflows/rspack-nextjs-build-integration-tests.yml @@ -99,7 +99,7 @@ jobs: name: test-reports-start-${{ matrix.group }} if-no-files-found: 'error' path: | - test/turbopack-test-junit-report + test/rspack-test-junit-report test-integration-production: name: Next.js integration test (Integration) diff --git a/.github/workflows/rspack-nextjs-dev-integration-tests.yml b/.github/workflows/rspack-nextjs-dev-integration-tests.yml index c137645ccbd7b0..b7eb142757402f 100644 --- a/.github/workflows/rspack-nextjs-dev-integration-tests.yml +++ b/.github/workflows/rspack-nextjs-dev-integration-tests.yml @@ -99,7 +99,7 @@ jobs: name: test-reports-dev-${{ matrix.group }} if-no-files-found: 'error' path: | - test/turbopack-test-junit-report + test/rspack-test-junit-report test-integration-development: name: Next.js integration test (Integration) @@ -149,7 +149,7 @@ jobs: name: test-reports-dev-integration-${{ matrix.group }} if-no-files-found: 'error' path: | - test/turbopack-test-junit-report + test/rspack-test-junit-report # Collect integration test results from execute_tests, # Store it as github artifact for next step to consume. diff --git a/.github/workflows/rspack-update-tests-manifest.yml b/.github/workflows/rspack-update-tests-manifest.yml index 8af7d5d50e4d73..f867d51201ea91 100644 --- a/.github/workflows/rspack-update-tests-manifest.yml +++ b/.github/workflows/rspack-update-tests-manifest.yml @@ -38,11 +38,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PULL_REQUESTS }} BRANCH_NAME: rspack-manifest - SCRIPT: test/build-turbopack-dev-tests-manifest.js + SCRIPT: test/build-rspack-dev-tests-manifest.js PR_TITLE: Update Rspack development test manifest PR_BODY: This auto-generated PR updates the development integration test manifest used when testing Rspack. update_build_manifest: - name: Update and upload Turbopack production test manifest + name: Update and upload Rspack production test manifest if: github.repository_owner == 'vercel' runs-on: ubuntu-latest steps: @@ -71,6 +71,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN_PULL_REQUESTS }} BRANCH_NAME: rspack-manifest - SCRIPT: test/build-turbopack-build-tests-manifest.js + SCRIPT: test/build-rspack-build-tests-manifest.js PR_TITLE: Update Rspack production test manifest PR_BODY: This auto-generated PR updates the production integration test manifest used when testing Rspack. diff --git a/jest.config.js b/jest.config.js index cf47e8b8b9276d..c9d2ebf13cf826 100644 --- a/jest.config.js +++ b/jest.config.js @@ -36,9 +36,14 @@ if (enableTestReport) { customJestConfig.reporters = ['default'] } - const outputDirectory = process.env.TURBOPACK - ? '/turbopack-test-junit-report' - : '/test-junit-report' + let outputDirectory + if (process.env.TURBOPACK) { + outputDirectory = '/turbopack-test-junit-report' + } else if (process.env.NEXT_RSPACK) { + outputDirectory = '/rspack-test-junit-report' + } else { + outputDirectory = '/test-junit-report' + } customJestConfig.reporters.push([ 'jest-junit', diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 4110104e36bff7..2b9e9d8790f131 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -1182,7 +1182,8 @@ export default abstract class Server< let params: ParsedUrlQuery | false = {} let paramsResult = utils.normalizeDynamicRouteParams( - parsedUrl.query + parsedUrl.query, + false ) // for prerendered ISR paths we attempt parsing the route @@ -1196,7 +1197,7 @@ export default abstract class Server< let matcherParams = utils.dynamicRouteMatcher?.(normalizedUrlPath) if (matcherParams) { - utils.normalizeDynamicRouteParams(matcherParams) + utils.normalizeDynamicRouteParams(matcherParams, false) Object.assign(paramsResult.params, matcherParams) paramsResult.hasValidParams = true } @@ -1218,8 +1219,10 @@ export default abstract class Server< let matcherParams = utils.dynamicRouteMatcher?.(matchedPath) if (matcherParams) { - const curParamsResult = - utils.normalizeDynamicRouteParams(matcherParams) + const curParamsResult = utils.normalizeDynamicRouteParams( + matcherParams, + false + ) if (curParamsResult.hasValidParams) { Object.assign(params, matcherParams) @@ -1254,6 +1257,19 @@ export default abstract class Server< } } + // Try to parse the params from the query if we couldn't parse them + // from the route matches but ignore missing optional params. + if (!paramsResult.hasValidParams) { + paramsResult = utils.normalizeDynamicRouteParams( + parsedUrl.query, + true + ) + + if (paramsResult.hasValidParams) { + params = paramsResult.params + } + } + // handle the actual dynamic route name being requested if ( utils.defaultRouteMatches && @@ -1276,7 +1292,7 @@ export default abstract class Server< } if (pageIsDynamic || didRewrite) { - utils.normalizeVercelUrl(req, true, [ + utils.normalizeVercelUrl(req, [ ...rewriteParamKeys, ...Object.keys(utils.defaultRouteRegex?.groups || {}), ]) diff --git a/packages/next/src/server/dev/hot-middleware.ts b/packages/next/src/server/dev/hot-middleware.ts index b3af17caa18232..952bf60c27772a 100644 --- a/packages/next/src/server/dev/hot-middleware.ts +++ b/packages/next/src/server/dev/hot-middleware.ts @@ -72,16 +72,11 @@ class EventStream { this.clients = new Set() } - everyClient(fn: (client: ws) => void) { - for (const client of this.clients) { - fn(client) - } - } - close() { - this.everyClient((client) => { - client.close() - }) + for (const wsClient of this.clients) { + // it's okay to not cleanly close these websocket connections, this is dev + wsClient.terminate() + } this.clients.clear() } @@ -93,9 +88,9 @@ class EventStream { } publish(payload: any) { - this.everyClient((client) => { - client.send(JSON.stringify(payload)) - }) + for (const wsClient of this.clients) { + wsClient.send(JSON.stringify(payload)) + } } } diff --git a/packages/next/src/server/dev/hot-reloader-turbopack.ts b/packages/next/src/server/dev/hot-reloader-turbopack.ts index 2b224932a9ae4c..721bb9d31627ba 100644 --- a/packages/next/src/server/dev/hot-reloader-turbopack.ts +++ b/packages/next/src/server/dev/hot-reloader-turbopack.ts @@ -1059,6 +1059,13 @@ export async function createHotReloaderTurbopack( } }) }, + close() { + for (const wsClient of clients) { + // it's okay to not cleanly close these websocket connections, this is dev + wsClient.terminate() + } + clients.clear() + }, } handleEntrypointsSubscription().catch((err) => { diff --git a/packages/next/src/server/dev/hot-reloader-types.ts b/packages/next/src/server/dev/hot-reloader-types.ts index f7e014f331e966..e8e27661a38474 100644 --- a/packages/next/src/server/dev/hot-reloader-types.ts +++ b/packages/next/src/server/dev/hot-reloader-types.ts @@ -182,4 +182,5 @@ export interface NextJsHotReloaderInterface { definition: RouteDefinition | undefined url?: string }): Promise + close(): void } diff --git a/packages/next/src/server/dev/hot-reloader-webpack.ts b/packages/next/src/server/dev/hot-reloader-webpack.ts index 27772d9127a777..7d0e16d04061a4 100644 --- a/packages/next/src/server/dev/hot-reloader-webpack.ts +++ b/packages/next/src/server/dev/hot-reloader-webpack.ts @@ -1595,4 +1595,8 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { }) }) } + + public close() { + this.webpackHotMiddleware?.close() + } } diff --git a/packages/next/src/server/lib/dev-bundler-service.ts b/packages/next/src/server/lib/dev-bundler-service.ts index 3cd148df9d7a55..6b2b35296468aa 100644 --- a/packages/next/src/server/lib/dev-bundler-service.ts +++ b/packages/next/src/server/lib/dev-bundler-service.ts @@ -103,4 +103,8 @@ export class DevBundlerService { data: this.appIsrManifest, }) } + + public close() { + this.bundler.hotReloader.close() + } } diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index e9834930ba523f..c50a73d5c106aa 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -9,6 +9,8 @@ export type ServerInitResult = { requestHandler: RequestHandler upgradeHandler: UpgradeHandler server: NextServer + // Make an effort to close upgraded HTTP requests (e.g. Turbopack HMR websockets) + closeUpgraded: () => void } let initializations: Record | undefined> = {} @@ -109,6 +111,9 @@ async function initializeImpl(opts: { requestHandler, upgradeHandler, server, + closeUpgraded() { + opts.bundlerService?.close() + }, } } diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 5c1750d2a1c89a..3ac0474555b6e1 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -751,5 +751,12 @@ export async function initialize(opts: { } } - return { requestHandler, upgradeHandler, server: handlers.server } + return { + requestHandler, + upgradeHandler, + server: handlers.server, + closeUpgraded() { + developmentBundler?.hotReloader?.close() + }, + } } diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 87cd0440fa252b..8ec1ad1b671d1c 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -289,6 +289,7 @@ export async function startServer( try { let cleanupStarted = false + let closeUpgraded: (() => void) | null = null const cleanup = () => { if (cleanupStarted) { // We can get duplicate signals, e.g. when `ctrl+c` is used in an @@ -303,12 +304,16 @@ export async function startServer( // first, stop accepting new connections and finish pending requests, // because they might affect `nextServer.close()` (e.g. by scheduling an `after`) - await new Promise((res) => + await new Promise((res) => { server.close((err) => { if (err) console.error(err) res() }) - ) + if (isDev) { + server.closeAllConnections() + closeUpgraded?.() + } + }) // now that no new requests can come in, clean up the rest await Promise.all([ @@ -360,6 +365,7 @@ export async function startServer( requestHandler = initResult.requestHandler upgradeHandler = initResult.upgradeHandler nextServer = initResult.server + closeUpgraded = initResult.closeUpgraded const startServerProcessDuration = performance.mark('next-start-end') && diff --git a/packages/next/src/server/server-utils.ts b/packages/next/src/server/server-utils.ts index 71106659393179..e8ad3fac9fd977 100644 --- a/packages/next/src/server/server-utils.ts +++ b/packages/next/src/server/server-utils.ts @@ -24,36 +24,34 @@ import { normalizeNextQueryParam } from './web/utils' export function normalizeVercelUrl( req: BaseNextRequest, - trustQuery: boolean, - paramKeys?: string[], - pageIsDynamic?: boolean, - defaultRouteRegex?: ReturnType | undefined + paramKeys: string[], + defaultRouteRegex: ReturnType | undefined ) { + if (!defaultRouteRegex) return + // make sure to normalize req.url on Vercel to strip dynamic params // from the query which are added during routing - if (pageIsDynamic && trustQuery && defaultRouteRegex) { - const _parsedUrl = parseUrl(req.url!, true) - delete (_parsedUrl as any).search - - for (const key of Object.keys(_parsedUrl.query)) { - const isNextQueryPrefix = - key !== NEXT_QUERY_PARAM_PREFIX && - key.startsWith(NEXT_QUERY_PARAM_PREFIX) - - const isNextInterceptionMarkerPrefix = - key !== NEXT_INTERCEPTION_MARKER_PREFIX && - key.startsWith(NEXT_INTERCEPTION_MARKER_PREFIX) - - if ( - isNextQueryPrefix || - isNextInterceptionMarkerPrefix || - (paramKeys || Object.keys(defaultRouteRegex.groups)).includes(key) - ) { - delete _parsedUrl.query[key] - } + const _parsedUrl = parseUrl(req.url!, true) + delete (_parsedUrl as any).search + + for (const key of Object.keys(_parsedUrl.query)) { + const isNextQueryPrefix = + key !== NEXT_QUERY_PARAM_PREFIX && key.startsWith(NEXT_QUERY_PARAM_PREFIX) + + const isNextInterceptionMarkerPrefix = + key !== NEXT_INTERCEPTION_MARKER_PREFIX && + key.startsWith(NEXT_INTERCEPTION_MARKER_PREFIX) + + if ( + isNextQueryPrefix || + isNextInterceptionMarkerPrefix || + (paramKeys || Object.keys(defaultRouteRegex.groups)).includes(key) + ) { + delete _parsedUrl.query[key] } - req.url = formatUrl(_parsedUrl) } + + req.url = formatUrl(_parsedUrl) } export function interpolateDynamicPath( @@ -89,27 +87,21 @@ export function interpolateDynamicPath( } export function normalizeDynamicRouteParams( - params: ParsedUrlQuery, - ignoreOptional?: boolean, - defaultRouteRegex?: ReturnType | undefined, - defaultRouteMatches?: ParsedUrlQuery | undefined + query: ParsedUrlQuery, + defaultRouteRegex: ReturnType, + defaultRouteMatches: ParsedUrlQuery, + ignoreMissingOptional: boolean ) { let hasValidParams = true - if (!defaultRouteRegex) return { params, hasValidParams: false } + let params: ParsedUrlQuery = {} - params = Object.keys(defaultRouteRegex.groups).reduce((prev, key) => { - let value: string | string[] | undefined = params[key] + for (const key of Object.keys(defaultRouteRegex.groups)) { + let value: string | string[] | undefined = query[key] if (typeof value === 'string') { value = normalizeRscURL(value) - } - if (Array.isArray(value)) { - value = value.map((val) => { - if (typeof val === 'string') { - val = normalizeRscURL(val) - } - return val - }) + } else if (Array.isArray(value)) { + value = value.map(normalizeRscURL) } // if the value matches the default value we can't rely @@ -128,9 +120,9 @@ export function normalizeDynamicRouteParams( if ( isDefaultValue || - (typeof value === 'undefined' && !(isOptional && ignoreOptional)) + (typeof value === 'undefined' && !(isOptional && ignoreMissingOptional)) ) { - hasValidParams = false + return { params: {}, hasValidParams: false } } // non-provided optional values should be undefined so normalize @@ -145,7 +137,7 @@ export function normalizeDynamicRouteParams( (value[0] === 'index' || value[0] === `[[...${key}]]`))) ) { value = undefined - delete params[key] + delete query[key] } // query values from the proxy aren't already split into arrays @@ -159,10 +151,9 @@ export function normalizeDynamicRouteParams( } if (value) { - prev[key] = value + params[key] = value } - return prev - }, {} as ParsedUrlQuery) + } return { params, @@ -375,28 +366,30 @@ export function getUtils({ dynamicRouteMatcher, defaultRouteMatches, getParamsFromRouteMatches, + /** + * Normalize dynamic route params. + * + * @param query - The query params to normalize. + * @param ignoreMissingOptional - Whether to ignore missing optional params. + * @returns The normalized params and whether they are valid. + */ normalizeDynamicRouteParams: ( - params: ParsedUrlQuery, - ignoreOptional?: boolean - ) => - normalizeDynamicRouteParams( - params, - ignoreOptional, + query: ParsedUrlQuery, + ignoreMissingOptional: boolean + ) => { + if (!defaultRouteRegex || !defaultRouteMatches) { + return { params: {}, hasValidParams: false } + } + + return normalizeDynamicRouteParams( + query, defaultRouteRegex, - defaultRouteMatches - ), - normalizeVercelUrl: ( - req: BaseNextRequest, - trustQuery: boolean, - paramKeys?: string[] - ) => - normalizeVercelUrl( - req, - trustQuery, - paramKeys, - pageIsDynamic, - defaultRouteRegex - ), + defaultRouteMatches, + ignoreMissingOptional + ) + }, + normalizeVercelUrl: (req: BaseNextRequest, paramKeys: string[]) => + normalizeVercelUrl(req, paramKeys, defaultRouteRegex), interpolateDynamicPath: ( pathname: string, params: Record diff --git a/packages/next/src/server/web-server.ts b/packages/next/src/server/web-server.ts index ca8904d47f7316..74adc0b805a81a 100644 --- a/packages/next/src/server/web-server.ts +++ b/packages/next/src/server/web-server.ts @@ -173,9 +173,9 @@ export default class NextWebServer extends BaseServer< ) as NextParsedUrlQuery const paramsResult = normalizeDynamicRouteParams( query, - false, routeRegex, - defaultRouteMatches + defaultRouteMatches, + false ) const normalizedParams = paramsResult.hasValidParams ? paramsResult.params @@ -186,13 +186,7 @@ export default class NextWebServer extends BaseServer< normalizedParams, routeRegex ) - normalizeVercelUrl( - req, - true, - Object.keys(routeRegex.routeKeys), - true, - routeRegex - ) + normalizeVercelUrl(req, Object.keys(routeRegex.routeKeys), routeRegex) } } diff --git a/packages/next/src/server/web/edge-route-module-wrapper.ts b/packages/next/src/server/web/edge-route-module-wrapper.ts index f0906ca3e3c6fa..4b45f01666a8d4 100644 --- a/packages/next/src/server/web/edge-route-module-wrapper.ts +++ b/packages/next/src/server/web/edge-route-module-wrapper.ts @@ -82,7 +82,8 @@ export class EdgeRouteModuleWrapper { }) const { params } = utils.normalizeDynamicRouteParams( - searchParamsToUrlQuery(request.nextUrl.searchParams) + searchParamsToUrlQuery(request.nextUrl.searchParams), + false ) const waitUntil = evt.waitUntil.bind(evt) diff --git a/test/.gitignore b/test/.gitignore index 83e38f361cec1a..15b4698e6bab3e 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -5,5 +5,6 @@ e2e/**/tsconfig.json production/**/tsconfig.json development/**/tsconfig.json +rspack-test-junit-report/ test-junit-report/ -turbopack-test-junit-report/ \ No newline at end of file +turbopack-test-junit-report/ diff --git a/test/integration/required-server-files-ssr-404/pages/partial-catch-all/[domain]/[[...rest]].js b/test/integration/required-server-files-ssr-404/pages/partial-catch-all/[domain]/[[...rest]].js new file mode 100644 index 00000000000000..52ed2b5d6b81d7 --- /dev/null +++ b/test/integration/required-server-files-ssr-404/pages/partial-catch-all/[domain]/[[...rest]].js @@ -0,0 +1,29 @@ +import { useRouter } from 'next/router' + +export const getStaticProps = ({ params }) => { + return { + props: { + hello: 'world', + params: params || null, + random: Math.random(), + }, + } +} + +export const getStaticPaths = () => { + return { + paths: ['/partial-catch-all/hello.com'], + fallback: true, + } +} + +export default function Page(props) { + const router = useRouter() + return ( + <> +

partial optional catch-all page

+

{JSON.stringify(router)}

+

{JSON.stringify(props)}

+ + ) +} diff --git a/test/integration/required-server-files-ssr-404/test/index.test.js b/test/integration/required-server-files-ssr-404/test/index.test.js index e18274c2a424b4..1df3ef4a2cee90 100644 --- a/test/integration/required-server-files-ssr-404/test/index.test.js +++ b/test/integration/required-server-files-ssr-404/test/index.test.js @@ -328,7 +328,7 @@ describe('Required Server Files', () => { { headers: { 'x-matched-path': '/catch-all/[[...rest]]', - 'x-now-route-matches': 'nxtPrest=hello&catchAll=hello', + 'x-now-route-matches': 'nxtPrest=hello', }, } ) @@ -342,7 +342,7 @@ describe('Required Server Files', () => { const html3 = await renderViaHTTP( appPort, - '/catch-all/[[..rest]]', + '/catch-all/[[...rest]]', undefined, { headers: { @@ -359,6 +359,98 @@ describe('Required Server Files', () => { expect(data3.params).toEqual({ rest: ['hello', 'world'] }) expect(isNaN(data3.random)).toBe(false) expect(data3.random).not.toBe(data.random) + + const html4 = await renderViaHTTP( + appPort, + '/catch-all/[[...rest]]', + { nxtPrest: 'frank' }, + { + headers: { + 'x-matched-path': '/catch-all/[[...rest]]', + }, + } + ) + const $4 = cheerio.load(html4) + const data4 = JSON.parse($4('#props').text()) + + expect($4('#catch-all').text()).toBe('optional catch-all page') + expect(data4.params).toEqual({ rest: ['frank'] }) + expect(isNaN(data4.random)).toBe(false) + expect(data4.random).not.toBe(data.random) + + const html5 = await renderViaHTTP( + appPort, + '/catch-all/[[...rest]]', + {}, + { + headers: { + 'x-matched-path': '/catch-all/[[...rest]]', + }, + } + ) + const $5 = cheerio.load(html5) + const data5 = JSON.parse($5('#props').text()) + + expect($5('#catch-all').text()).toBe('optional catch-all page') + expect(data5.params).toEqual({}) + expect(isNaN(data5.random)).toBe(false) + expect(data5.random).not.toBe(data.random) + + const html6 = await renderViaHTTP( + appPort, + '/catch-all/[[...rest]]', + { nxtPrest: 'frank' }, + { + headers: { + 'x-matched-path': '/catch-all/[[...rest]]', + }, + } + ) + const $6 = cheerio.load(html6) + const data6 = JSON.parse($6('#props').text()) + + expect($6('#catch-all').text()).toBe('optional catch-all page') + expect(data6.params).toEqual({ rest: ['frank'] }) + expect(isNaN(data6.random)).toBe(false) + expect(data6.random).not.toBe(data.random) + }) + + describe('partial optional catch-all route', () => { + it.each([ + { + path: '/partial-catch-all/hello.com', + query: { nxtPdomain: 'hello.com' }, + expected: { domain: 'hello.com' }, + }, + { + path: '/partial-catch-all/hello.com/hello', + query: { nxtPdomain: 'hello.com', nxtPrest: 'hello' }, + expected: { domain: 'hello.com', rest: ['hello'] }, + }, + { + path: '/partial-catch-all/hello.com/hello/world', + query: { nxtPdomain: 'hello.com', nxtPrest: 'hello/world' }, + expected: { domain: 'hello.com', rest: ['hello', 'world'] }, + }, + ])('should render $path', async ({ query, expected }) => { + const html = await renderViaHTTP( + appPort, + '/partial-catch-all/[domain]/[[...rest]]', + query, + { + headers: { + 'x-matched-path': '/partial-catch-all/[domain]/[[...rest]]', + }, + } + ) + + const $ = cheerio.load(html) + const data = JSON.parse($('#props').text()) + + expect($('#catch-all').text()).toBe('partial optional catch-all page') + expect(data.params).toEqual(expected) + expect(data.hello).toBe('world') + }) }) it('should return data correctly with x-matched-path for optional catch-all route', async () => { diff --git a/test/production/standalone-mode/required-server-files/required-server-files.test.ts b/test/production/standalone-mode/required-server-files/required-server-files.test.ts index 06006c64d3adce..caf296075f07a0 100644 --- a/test/production/standalone-mode/required-server-files/required-server-files.test.ts +++ b/test/production/standalone-mode/required-server-files/required-server-files.test.ts @@ -244,6 +244,42 @@ describe('required server files', () => { } ) + it('should handle data routes with optional catch-all params', async () => { + let res = await fetchViaHTTP( + appPort, + `/_next/data/${next.buildId}/catch-all.json`, + {}, + { + headers: { + 'x-matched-path': `/_next/data/${next.buildId}/catch-all.json`, + }, + } + ) + expect(res.status).toBe(200) + + let json = await res.json() + expect(json.pageProps.params).toEqual({ + rest: undefined, + }) + + res = await fetchViaHTTP( + appPort, + `/_next/data/${next.buildId}/catch-all/next.js.json`, + {}, + { + headers: { + 'x-matched-path': `/_next/data/${next.buildId}/catch-all/next.js.json`, + }, + } + ) + expect(res.status).toBe(200) + + json = await res.json() + expect(json.pageProps.params).toEqual({ + rest: ['next.js'], + }) + }) + it.each([ { case: 'notFound no revalidate', diff --git a/test/turbopack-build-tests-manifest.json b/test/turbopack-build-tests-manifest.json index 18d09096449e50..9b4c76d256ca50 100644 --- a/test/turbopack-build-tests-manifest.json +++ b/test/turbopack-build-tests-manifest.json @@ -14483,6 +14483,9 @@ "Required Server Files production mode should render dynamic SSR page correctly", "Required Server Files production mode should render dynamic SSR page correctly with x-matched-path", "Required Server Files production mode should render fallback optional catch-all route correctly with x-matched-path and routes-matches", + "Required Server Files production mode partial optional catch-all route should render /partial-catch-all/hello.com", + "Required Server Files production mode partial optional catch-all route should render /partial-catch-all/hello.com/hello", + "Required Server Files production mode partial optional catch-all route should render /partial-catch-all/hello.com/hello/world", "Required Server Files production mode should render fallback page correctly", "Required Server Files production mode should render fallback page correctly with x-matched-path and routes-matches", "Required Server Files production mode should return data correctly with x-matched-path", @@ -16646,6 +16649,7 @@ "required server files should have correct cache-control for notFound with revalidate", "required server files should have correct cache-control for redirect no revalidate", "required server files should have correct cache-control for redirect with revalidate", + "required server files should handle data routes with optional catch-all params", "required server files should have correct resolvedUrl from dynamic route", "required server files should have correct resolvedUrl from rewrite", "required server files should have correct resolvedUrl from rewrite with added query", diff --git a/test/turbopack-dev-tests-manifest.json b/test/turbopack-dev-tests-manifest.json index fe4d95d4bb7597..e51c012b2922d8 100644 --- a/test/turbopack-dev-tests-manifest.json +++ b/test/turbopack-dev-tests-manifest.json @@ -18641,6 +18641,9 @@ "Required Server Files production mode should render dynamic SSR page correctly", "Required Server Files production mode should render dynamic SSR page correctly with x-matched-path", "Required Server Files production mode should render fallback optional catch-all route correctly with x-matched-path and routes-matches", + "Required Server Files production mode partial optional catch-all route should render /partial-catch-all/hello.com", + "Required Server Files production mode partial optional catch-all route should render /partial-catch-all/hello.com/hello", + "Required Server Files production mode partial optional catch-all route should render /partial-catch-all/hello.com/hello/world", "Required Server Files production mode should render fallback page correctly", "Required Server Files production mode should render fallback page correctly with x-matched-path and routes-matches", "Required Server Files production mode should return data correctly with x-matched-path",