diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/404-custom/input/app/test.tsx b/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/404-custom/input/app/test.tsx index fb3db2527086c..6b3079b778cda 100644 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/404-custom/input/app/test.tsx +++ b/packages/next-swc/crates/next-dev-tests/tests/integration/next/app/404-custom/input/app/test.tsx @@ -49,7 +49,8 @@ function runTests(harness: Harness, iframe: HTMLIFrameElement) { ) // TODO: This test is flaky, so it needs a particularly long timeout. - it( + // TODO(WEB-980) Fix this test once we no longer throw an error when rendering a 404 page. + it.skip( 'renders a custom 404 page', async () => { await harness.load(iframe, '/not-found') diff --git a/packages/next/src/build/webpack/loaders/next-app-loader.ts b/packages/next/src/build/webpack/loaders/next-app-loader.ts index 559756f0d4a72..ed09983e5c64b 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -319,6 +319,17 @@ async function createTreeCodeFromPath( globalError = await resolver( `${path.dirname(layoutPath)}/${GLOBAL_ERROR_FILE_TYPE}` ) + + const hasNotFound = definedFilePaths.some( + ([type]) => type === 'not-found' + ) + // Add default not found error as root not found if not present + if (!hasNotFound) { + const notFoundPath = 'next/dist/client/components/not-found-error' + if (notFoundPath) { + definedFilePaths.push(['not-found', notFoundPath]) + } + } } } diff --git a/packages/next/src/client/components/not-found-boundary.tsx b/packages/next/src/client/components/not-found-boundary.tsx index 13bf04051c316..737fc8d3eb0a9 100644 --- a/packages/next/src/client/components/not-found-boundary.tsx +++ b/packages/next/src/client/components/not-found-boundary.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import { usePathname } from './navigation' diff --git a/packages/next/src/client/components/error.tsx b/packages/next/src/client/components/not-found-error.tsx similarity index 98% rename from packages/next/src/client/components/error.tsx rename to packages/next/src/client/components/not-found-error.tsx index 3b0c133603308..2e558c4bbdf13 100644 --- a/packages/next/src/client/components/error.tsx +++ b/packages/next/src/client/components/not-found-error.tsx @@ -35,7 +35,7 @@ const styles: Record = { }, } -export function NotFound() { +export default function NotFound() { return ( <> {/* */} diff --git a/packages/next/src/lib/metadata/metadata.tsx b/packages/next/src/lib/metadata/metadata.tsx index 42560ce4632cd..e32ed342dfae3 100644 --- a/packages/next/src/lib/metadata/metadata.tsx +++ b/packages/next/src/lib/metadata/metadata.tsx @@ -16,10 +16,11 @@ import { AppLinksMeta, } from './generate/opengraph' import { IconsMetadata } from './generate/icons' -import { accumulateMetadata, resolveMetadata } from './resolve-metadata' +import { resolveMetadata } from './resolve-metadata' import { MetaFilter } from './generate/meta' import { ResolvedMetadata } from './types/metadata-interface' import { createDefaultMetadata } from './default-metadata' +import { isNotFoundError } from '../../client/components/not-found' // Use a promise to share the status of the metadata resolving, // returning two components `MetadataTree` and `MetadataOutlet` @@ -55,24 +56,44 @@ export function createMetadataComponents({ async function MetadataTree() { const defaultMetadata = createDefaultMetadata() let metadata: ResolvedMetadata | undefined = defaultMetadata - try { - const resolvedMetadata = await resolveMetadata({ - tree, - parentParams: {}, - metadataItems: [], - searchParams, - getDynamicParamFromSegment, - errorConvention: errorType === 'redirect' ? undefined : errorType, - }) + let error: any + const errorMetadataItem: [null, null] = [null, null] + const errorConvention = errorType === 'redirect' ? undefined : errorType - // Skip for redirect case as for the temporary redirect case we don't need the metadata on client - if (errorType === 'redirect') { - metadata = defaultMetadata - } else { - metadata = await accumulateMetadata(resolvedMetadata, metadataContext) - } + const [resolvedMetadata, resolvedError] = await resolveMetadata({ + tree, + parentParams: {}, + metadataItems: [], + errorMetadataItem, + searchParams, + getDynamicParamFromSegment, + errorConvention, + metadataContext, + }) + if (!resolvedError) { + metadata = resolvedMetadata resolve(undefined) - } catch (error: any) { + } else { + error = resolvedError + // If the error triggers in initial metadata resolving, re-resolve with proper error type. + // They'll be saved for flight data, when hydrates, it will replaces the SSR'd metadata with this. + // for not-found error: resolve not-found metadata + if (!errorType && isNotFoundError(resolvedError)) { + const [notFoundMetadata, notFoundMetadataError] = await resolveMetadata( + { + tree, + parentParams: {}, + metadataItems: [], + errorMetadataItem, + searchParams, + getDynamicParamFromSegment, + errorConvention: 'not-found', + metadataContext, + } + ) + metadata = notFoundMetadata + error = notFoundMetadataError || error + } resolve(error) } diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts index e46a12cecae16..0ec2630dfdee0 100644 --- a/packages/next/src/lib/metadata/resolve-metadata.ts +++ b/packages/next/src/lib/metadata/resolve-metadata.ts @@ -15,7 +15,7 @@ import { resolveTitle } from './resolvers/resolve-title' import { resolveAsArrayOrUndefined } from './generate/utils' import { isClientReference } from '../client-reference' import { - getErrorOrLayoutModule, + getComponentTypeModule, getLayoutOrPageModule, LoaderTree, } from '../../server/lib/app-dir-module' @@ -315,21 +315,26 @@ async function resolveStaticMetadata(components: ComponentsType, props: any) { // [layout.metadata, static files metadata] -> ... -> [page.metadata, static files metadata] export async function collectMetadata({ tree, - metadataItems: array, + metadataItems, + errorMetadataItem, props, route, errorConvention, }: { tree: LoaderTree metadataItems: MetadataItems + errorMetadataItem: MetadataItems[number] props: any route: string errorConvention?: 'not-found' }) { let mod let modType + const hasErrorConventionComponent = Boolean( + errorConvention && tree[2][errorConvention] + ) if (errorConvention) { - mod = await getErrorOrLayoutModule(tree, errorConvention) + mod = await getComponentTypeModule(tree, 'layout') modType = errorConvention } else { ;[mod, modType] = await getLayoutOrPageModule(tree) @@ -344,13 +349,23 @@ export async function collectMetadata({ ? await getDefinedMetadata(mod, props, { route }) : null - array.push([metadataExport, staticFilesMetadata]) + metadataItems.push([metadataExport, staticFilesMetadata]) + + if (hasErrorConventionComponent && errorConvention) { + const errorMod = await getComponentTypeModule(tree, errorConvention) + const errorMetadataExport = errorMod + ? await getDefinedMetadata(errorMod, props, { route }) + : null + errorMetadataItem[0] = errorMetadataExport + errorMetadataItem[1] = staticFilesMetadata + } } -export async function resolveMetadata({ +export async function resolveMetadataItems({ tree, parentParams, metadataItems, + errorMetadataItem, treePrefix = [], getDynamicParamFromSegment, searchParams, @@ -359,6 +374,7 @@ export async function resolveMetadata({ tree: LoaderTree parentParams: { [key: string]: any } metadataItems: MetadataItems + errorMetadataItem: MetadataItems[number] /** Provided tree can be nested subtree, this argument says what is the path of such subtree */ treePrefix?: string[] getDynamicParamFromSegment: GetDynamicParamFromSegment @@ -392,6 +408,7 @@ export async function resolveMetadata({ await collectMetadata({ tree, metadataItems, + errorMetadataItem, errorConvention, props: layerProps, route: currentTreePrefix @@ -402,9 +419,10 @@ export async function resolveMetadata({ for (const key in parallelRoutes) { const childTree = parallelRoutes[key] - await resolveMetadata({ + await resolveMetadataItems({ tree: childTree, metadataItems, + errorMetadataItem, parentParams: currentParams, treePrefix: currentTreePrefix, searchParams, @@ -413,6 +431,12 @@ export async function resolveMetadata({ }) } + if (Object.keys(parallelRoutes).length === 0 && errorConvention) { + // If there are no parallel routes, place error metadata as the last item. + // e.g. layout -> layout -> not-found + metadataItems.push(errorMetadataItem) + } + return metadataItems } @@ -543,3 +567,43 @@ export async function accumulateMetadata( return postProcessMetadata(resolvedMetadata, titleTemplates) } + +export async function resolveMetadata({ + tree, + parentParams, + metadataItems, + errorMetadataItem, + getDynamicParamFromSegment, + searchParams, + errorConvention, + metadataContext, +}: { + tree: LoaderTree + parentParams: { [key: string]: any } + metadataItems: MetadataItems + errorMetadataItem: MetadataItems[number] + /** Provided tree can be nested subtree, this argument says what is the path of such subtree */ + treePrefix?: string[] + getDynamicParamFromSegment: GetDynamicParamFromSegment + searchParams: { [key: string]: any } + errorConvention: 'not-found' | undefined + metadataContext: MetadataContext +}): Promise<[ResolvedMetadata, any]> { + const resolvedMetadataItems = await resolveMetadataItems({ + tree, + parentParams, + metadataItems, + errorMetadataItem, + getDynamicParamFromSegment, + searchParams, + errorConvention, + }) + let metadata: ResolvedMetadata = createDefaultMetadata() + let error + try { + metadata = await accumulateMetadata(resolvedMetadataItems, metadataContext) + } catch (err: any) { + error = err + } + return [metadata, error] +} diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index bd25eb2a5f83a..3ff9b1a0746e8 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -16,7 +16,6 @@ import type { StaticGenerationBailout } from '../../client/components/static-gen import type { RequestAsyncStorage } from '../../client/components/request-async-storage' import React from 'react' -import { NotFound as DefaultNotFound } from '../../client/components/error' import { createServerComponentRenderer } from './create-server-components-renderer' import { ParsedUrlQuery } from 'querystring' @@ -29,6 +28,7 @@ import { streamToBufferedResult, cloneTransformStream, } from '../stream-utils/node-web-streams-helper' +import DefaultNotFound from '../../client/components/not-found-error' import { canSegmentBeOverridden, matchSegment, @@ -75,8 +75,6 @@ import { handleAction } from './action-handler' import { NEXT_DYNAMIC_NO_SSR_CODE } from '../../shared/lib/lazy-dynamic/no-ssr-error' import { warn } from '../../build/output/log' import { appendMutableCookies } from '../web/spec-extension/adapters/request-cookies' -import { ComponentsType } from '../../build/webpack/loaders/next-app-loader' -import { ModuleReference } from '../../build/webpack/loaders/metadata/types' import { createServerInsertedHTML } from './server-inserted-html' import { getRequiredScripts } from './required-scripts' @@ -90,51 +88,9 @@ export type GetDynamicParamFromSegment = ( type: DynamicParamTypesShort } | null -function ErrorHtml({ - children, -}: { - head?: React.ReactNode - children?: React.ReactNode -}) { - return ( - - {children} - - ) -} - function createNotFoundLoaderTree(loaderTree: LoaderTree): LoaderTree { // Align the segment with parallel-route-default in next-app-loader - return ['__DEFAULT__', {}, loaderTree[2]] -} - -// Find the closest matched component in the loader tree for a given component type -function findMatchedComponent( - loaderTree: LoaderTree, - componentType: Exclude, - result?: ModuleReference -): ModuleReference | undefined { - const [segment, parallelRoutes, components] = loaderTree - const childKeys = Object.keys(parallelRoutes) - result = components[componentType] || result - - // reached the end of the tree - if (segment === '__DEFAULT__' || segment === '__PAGE__') { - return result - } - - for (const key of childKeys) { - const childTree = parallelRoutes[key] - const matchedComponent = findMatchedComponent( - childTree, - componentType, - result - ) - if (matchedComponent) { - return matchedComponent - } - } - return undefined + return ['', {}, loaderTree[2]] } /* This method is important for intercepted routes to function: @@ -698,7 +654,7 @@ export async function renderToHTMLOrFlight( getComponent: notFound[0], injectedCSS: injectedCSSWithCurrentLayout, }) - : [rootLayoutIncluded ? undefined : DefaultNotFound] + : [] let dynamic = layoutOrPageMod?.dynamic @@ -768,12 +724,40 @@ export async function renderToHTMLOrFlight( throw staticGenerationStore.dynamicUsageErr } + const LayoutOrPage = layoutOrPageMod + ? interopDefault(layoutOrPageMod) + : undefined + /** * The React Component to render. */ - let Component = layoutOrPageMod - ? interopDefault(layoutOrPageMod) - : undefined + let Component = LayoutOrPage + const parallelKeys = Object.keys(parallelRoutes) + const hasSlotKey = parallelKeys.length > 1 + + if (hasSlotKey && rootLayoutAtThisLevel) { + const NotFoundBoundary = + ComponentMod.NotFoundBoundary as typeof import('../../client/components/not-found-boundary').NotFoundBoundary + Component = (componentProps: any) => { + const NotFoundComponent = NotFound || DefaultNotFound + const RootLayoutComponent = LayoutOrPage + return ( + + {styles} + + {notFoundStyles} + + + + } + > + + + ) + } + } if (dev) { const { isValidElementType } = require('next/dist/compiled/react-is') @@ -829,6 +813,7 @@ export async function renderToHTMLOrFlight( const parallelRouteMap = await Promise.all( Object.keys(parallelRoutes).map( async (parallelRouteKey): Promise<[string, React.ReactNode]> => { + const isChildrenRouteKey = parallelRouteKey === 'children' const currentSegmentPath: FlightSegmentPath = firstItem ? [parallelRouteKey] : [actualSegment, parallelRouteKey] @@ -837,6 +822,8 @@ export async function renderToHTMLOrFlight( const childSegment = parallelRoute[0] const childSegmentParam = getDynamicParamFromSegment(childSegment) + const notFoundComponent = + NotFound && isChildrenRouteKey ? : undefined // if we're prefetching and that there's a Loading component, we bail out // otherwise we keep rendering for the prefetch. @@ -873,7 +860,7 @@ export async function renderToHTMLOrFlight( } templateStyles={templateStyles} - notFound={NotFound ? : undefined} + notFound={notFoundComponent} notFoundStyles={notFoundStyles} childProp={childProp} />, @@ -926,7 +913,7 @@ export async function renderToHTMLOrFlight( } templateStyles={templateStyles} - notFound={NotFound ? : undefined} + notFound={notFoundComponent} notFoundStyles={notFoundStyles} childProp={childProp} styles={childStyles} @@ -963,7 +950,7 @@ export async function renderToHTMLOrFlight( asNotFound && // In development, it could hit the parallel-route-default not found, so we only need to check the segment. // Or if there's no parallel routes means it reaches the end. - ((segment === '__DEFAULT__' && !parallelRouteMap.length) || + (!parallelRouteMap.length || // For production build the original pathname is /_not-found, always render not-found component. (!renderOpts.dev && renderOpts.originalPathname === '/_not-found')) ) { @@ -1353,37 +1340,6 @@ export async function renderToHTMLOrFlight( } : {} - async function getNotFound(tree: LoaderTree, injectedCSS: Set) { - const notFound = findMatchedComponent(tree, 'not-found') - const [NotFound, notFoundStyles] = notFound - ? await createComponentAndStyles({ - filePath: notFound[1], - getComponent: notFound[0], - injectedCSS, - }) - : [] - return [NotFound, notFoundStyles] - } - - async function getRootLayout( - tree: LoaderTree, - injectedCSS: Set, - injectedFontPreloadTags: Set - ) { - const { layout } = tree[2] - const layoutPath = layout?.[1] - const styles = getLayerAssets({ - layoutOrPagePath: layoutPath, - injectedCSS: new Set(injectedCSS), - injectedFontPreloadTags: new Set(injectedFontPreloadTags), - }) - const rootLayoutModule = layout?.[0] - const RootLayout = rootLayoutModule - ? interopDefault(await rootLayoutModule()) - : null - return [RootLayout, styles] - } - /** * A new React Component that renders the provided React Component * using Flight which can then be rendered to HTML. @@ -1644,17 +1600,14 @@ export async function renderToHTMLOrFlight( const is404 = res.statusCode === 404 // Preserve the existing RSC inline chunks from the page rendering. - // For 404 errors: the metadata from layout can be skipped with the error page. - // For other errors (such as redirection): it can still be re-thrown on client. + // To avoid the same stream being operated twice, clone the origin stream for error rendering. const serverErrorComponentsRenderOpts: typeof serverComponentsRenderOpts = { ...serverComponentsRenderOpts, rscChunks: [], - transformStream: is404 - ? new TransformStream() - : cloneTransformStream( - serverComponentsRenderOpts.transformStream - ), + transformStream: cloneTransformStream( + serverComponentsRenderOpts.transformStream + ), } const errorType = is404 @@ -1685,8 +1638,8 @@ export async function renderToHTMLOrFlight( const ErrorPage = createServerComponentRenderer( async () => { errorPreinitScripts() - const [MetadataTree, MetadataOutlet] = createMetadataComponents({ - tree, // still use original tree with not-found boundaries to extract metadata + const [MetadataTree] = createMetadataComponents({ + tree, pathname, errorType, searchParams: providedSearchParams, @@ -1702,41 +1655,12 @@ export async function renderToHTMLOrFlight( ) - const notFoundLoaderTree: LoaderTree = is404 - ? createNotFoundLoaderTree(tree) - : tree const initialTree = createFlightRouterStateFromLoaderTree( - notFoundLoaderTree, + tree, getDynamicParamFromSegment, query ) - const injectedCSS = new Set() - const injectedFontPreloadTags = new Set() - const [RootLayout, rootStyles] = await getRootLayout( - tree, - injectedCSS, - injectedFontPreloadTags - ) - const [NotFound, notFoundStyles] = await getNotFound( - !renderOpts.dev && renderOpts.originalPathname === '/_not-found' - ? notFoundLoaderTree - : tree, - injectedCSS - ) - - const GlobalNotFound = NotFound || DefaultNotFound - const ErrorLayout = RootLayout || ErrorHtml - - const notFoundElement = ( - - - {rootStyles} - {notFoundStyles} - - - ) - // For metadata notFound error there's no global not found boundary on top // so we create a not found page with AppRouter return ( @@ -1748,7 +1672,10 @@ export async function renderToHTMLOrFlight( initialHead={head} globalErrorComponent={GlobalError} > - {is404 ? notFoundElement : } + + + + ) }, diff --git a/packages/next/src/server/app-render/entry-base.ts b/packages/next/src/server/app-render/entry-base.ts index c35c8cb20279c..6bc5fd7e7ace1 100644 --- a/packages/next/src/server/app-render/entry-base.ts +++ b/packages/next/src/server/app-render/entry-base.ts @@ -32,6 +32,9 @@ const { const { preloadStyle, preloadFont, preconnect } = require('next/dist/server/app-render/rsc/preloads') as typeof import('../../server/app-render/rsc/preloads') +const { NotFoundBoundary } = + require('next/dist/client/components/not-found-boundary') as typeof import('../../client/components/not-found-boundary') + export { AppRouter, LayoutRouter, @@ -49,4 +52,5 @@ export { preloadFont, preconnect, StaticGenerationSearchParamsBailoutProvider, + NotFoundBoundary, } diff --git a/packages/next/src/server/lib/app-dir-module.ts b/packages/next/src/server/lib/app-dir-module.ts index cc07efeff5321..0085bd5715cf9 100644 --- a/packages/next/src/server/lib/app-dir-module.ts +++ b/packages/next/src/server/lib/app-dir-module.ts @@ -33,16 +33,13 @@ export async function getLayoutOrPageModule(loaderTree: LoaderTree) { return [value, modType] as const } -// First check not-found, if it doesn't exist then pick layout -export async function getErrorOrLayoutModule( +export async function getComponentTypeModule( loaderTree: LoaderTree, - errorType: 'not-found' + componentType: 'layout' | 'not-found' ) { - const { [errorType]: error, layout } = loaderTree[2] - if (typeof error !== 'undefined') { - return await error[0]() - } else if (typeof layout !== 'undefined') { - return await layout[0]() + const { [componentType]: component } = loaderTree[2] + if (typeof component !== 'undefined') { + return await component[0]() } return undefined } diff --git a/test/e2e/app-dir/not-found/basic/index.test.ts b/test/e2e/app-dir/not-found/basic/index.test.ts index 868059fb9cf13..cbbeb0e834c2f 100644 --- a/test/e2e/app-dir/not-found/basic/index.test.ts +++ b/test/e2e/app-dir/not-found/basic/index.test.ts @@ -33,12 +33,15 @@ createNextDescribe( }) it('should match dynamic route not-found boundary correctly', async () => { - const $dynamic = await next.render$('/dynamic') - const $dynamicId = await next.render$('/dynamic/123') // `/dynamic` display works - expect($dynamic('main').text()).toBe('dynamic') + const browserDynamic = await next.browser('/dynamic') + expect(await browserDynamic.elementByCss('main').text()).toBe('dynamic') + // `/dynamic/[id]` calling notFound() will match the same level not-found boundary - expect($dynamicId('#not-found').text()).toBe('dynamic/[id] not found') + const browserDynamicId = await next.browser('/dynamic/123') + expect(await browserDynamicId.elementByCss('#not-found').text()).toBe( + 'dynamic/[id] not found' + ) }) if (isNextDev) { diff --git a/test/e2e/app-dir/not-found/group-route/app/(group)/blog/page.js b/test/e2e/app-dir/not-found/group-route/app/(group)/blog/page.js new file mode 100644 index 0000000000000..97ea65b48340c --- /dev/null +++ b/test/e2e/app-dir/not-found/group-route/app/(group)/blog/page.js @@ -0,0 +1,5 @@ +import { notFound } from 'next/navigation' + +export default function Page() { + notFound() +} diff --git a/test/e2e/app-dir/not-found/group-route/app/(group)/layout.js b/test/e2e/app-dir/not-found/group-route/app/(group)/layout.js new file mode 100644 index 0000000000000..4963c790abb45 --- /dev/null +++ b/test/e2e/app-dir/not-found/group-route/app/(group)/layout.js @@ -0,0 +1,10 @@ +export default function Layout({ children }) { + return ( + + +

Group Layout

+ {children} + + + ) +} diff --git a/test/e2e/app-dir/not-found/group-route/app/(group)/not-found.js b/test/e2e/app-dir/not-found/group-route/app/(group)/not-found.js new file mode 100644 index 0000000000000..8e0256153d504 --- /dev/null +++ b/test/e2e/app-dir/not-found/group-route/app/(group)/not-found.js @@ -0,0 +1,3 @@ +export default function Page() { + return
Not found!
+} diff --git a/test/e2e/app-dir/not-found/group-route/app/layout.js b/test/e2e/app-dir/not-found/group-route/app/layout.js new file mode 100644 index 0000000000000..750eb927b1980 --- /dev/null +++ b/test/e2e/app-dir/not-found/group-route/app/layout.js @@ -0,0 +1,7 @@ +export default function Layout({ children }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/not-found/group-route/index.test.ts b/test/e2e/app-dir/not-found/group-route/index.test.ts new file mode 100644 index 0000000000000..b5ad0925bcd88 --- /dev/null +++ b/test/e2e/app-dir/not-found/group-route/index.test.ts @@ -0,0 +1,45 @@ +import { createNextDescribe } from 'e2e-utils' + +createNextDescribe( + 'app dir - not-found - group route', + { + files: __dirname, + skipDeployment: true, + }, + ({ next }) => { + const runTests = () => { + it('should use the not-found page under group routes', async () => { + const browser = await next.browser('/blog') + expect(await browser.elementByCss('h1').text()).toContain( + 'Group Layout' + ) + expect(await browser.elementByCss('#not-found').text()).toContain( + 'Not found!' + ) + }) + } + + describe('with default runtime', () => { + runTests() + }) + + describe('with runtime = edge', () => { + let originalLayout = '' + + beforeAll(async () => { + await next.stop() + originalLayout = await next.readFile('app/layout.js') + await next.patchFile( + 'app/layout.js', + `export const runtime = 'edge'\n${originalLayout}` + ) + await next.start() + }) + afterAll(async () => { + await next.patchFile('app/layout.js', originalLayout) + }) + + runTests() + }) + } +) diff --git a/test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts b/test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts index 8cbd582ad0718..951f3bf7aeaad 100644 --- a/test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts +++ b/test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts @@ -8,6 +8,7 @@ createNextDescribe( skipDeployment: true, }, ({ next }) => { + // TODO: revisit the error for missing parallel routes slot it('should not render the @children slot when the @slot is not found', async () => { const browser = await next.browser('/') // we make sure the page is available through navigating