diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 65b62563154e..aed59e8e069d 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -69,12 +69,14 @@ export function createPagesMapping({ pageExtensions, pagePaths, pagesType, + pagesDir, }: { hasServerComponents: boolean isDev: boolean pageExtensions: string[] pagePaths: string[] pagesType: 'pages' | 'root' | 'app' + pagesDir: string | undefined }): { [page: string]: string } { const previousPages: { [key: string]: string } = {} const pages = pagePaths.reduce<{ [key: string]: string }>( @@ -133,7 +135,7 @@ export function createPagesMapping({ // In development we always alias these to allow Webpack to fallback to // the correct source file so that HMR can work properly when a file is // added or removed. - const root = isDev ? PAGES_DIR_ALIAS : 'next/dist/pages' + const root = isDev && pagesDir ? PAGES_DIR_ALIAS : 'next/dist/pages' return { '/_app': `${root}/_app`, @@ -149,7 +151,7 @@ interface CreateEntrypointsParams { envFiles: LoadedEnvFiles isDev?: boolean pages: { [page: string]: string } - pagesDir: string + pagesDir?: string previewMode: __ApiPreviewProps rootDir: string rootPaths?: Record @@ -366,7 +368,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { // Handle paths that have aliases const pageFilePath = (() => { - if (absolutePagePath.startsWith(PAGES_DIR_ALIAS)) { + if (absolutePagePath.startsWith(PAGES_DIR_ALIAS) && pagesDir) { return absolutePagePath.replace(PAGES_DIR_ALIAS, pagesDir) } @@ -444,12 +446,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { }) } } else { - server[serverBundlePath] = isServerComponent - ? { - import: mappings[page], - layer: WEBPACK_LAYERS.server, - } - : [mappings[page]] + server[serverBundlePath] = [mappings[page]] } }, onEdgeServer: () => { @@ -490,7 +487,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { await Promise.all(Object.keys(pages).map(getEntryHandler(pages, 'pages'))) if (nestedMiddleware.length > 0) { - throw new NestedMiddlewareError(nestedMiddleware, rootDir, pagesDir) + throw new NestedMiddlewareError(nestedMiddleware, rootDir, pagesDir!) } return { diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index f9097ff16470..5f107284eb66 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -314,7 +314,9 @@ export default async function build( eventCliSession(dir, config, { webpackVersion: 5, cliCommand: 'build', - isSrcDir: path.relative(dir, pagesDir!).startsWith('src'), + isSrcDir: + (!!pagesDir && path.relative(dir, pagesDir).startsWith('src')) || + (!!appDir && path.relative(dir, appDir).startsWith('src')), hasNowJson: !!(await findUp('now.json', { cwd: dir })), isCustomServer: null, }) @@ -395,7 +397,8 @@ export default async function build( config.eslint?.dirs, config.experimental.cpus, config.experimental.workerThreads, - telemetry + telemetry, + !!config.experimental.appDir ) }), ]) @@ -436,14 +439,16 @@ export default async function build( const isLikeServerless = isTargetLikeServerless(target) - const pagesPaths = await nextBuildSpan - .traceChild('collect-pages') - .traceAsyncFn(() => - recursiveReadDir( - pagesDir, - new RegExp(`\\.(?:${config.pageExtensions.join('|')})$`) - ) - ) + const pagesPaths = pagesDir + ? await nextBuildSpan + .traceChild('collect-pages') + .traceAsyncFn(() => + recursiveReadDir( + pagesDir, + new RegExp(`\\.(?:${config.pageExtensions.join('|')})$`) + ) + ) + : [] let appPaths: string[] | undefined @@ -462,9 +467,11 @@ export default async function build( `^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join('|')})$` ) - const rootPaths = ( - await flatReaddir(join(pagesDir, '..'), middlewareDetectionRegExp) - ).map((absoluteFile) => absoluteFile.replace(dir, '')) + const rootPaths = pagesDir + ? ( + await flatReaddir(join(pagesDir, '..'), middlewareDetectionRegExp) + ).map((absoluteFile) => absoluteFile.replace(dir, '')) + : [] // needed for static exporting since we want to replace with HTML // files @@ -487,6 +494,7 @@ export default async function build( pageExtensions: config.pageExtensions, pagesType: 'pages', pagePaths: pagesPaths, + pagesDir, }) ) @@ -502,6 +510,7 @@ export default async function build( isDev: false, pagesType: 'app', pageExtensions: config.pageExtensions, + pagesDir: pagesDir, }) ) } @@ -514,6 +523,7 @@ export default async function build( pageExtensions: config.pageExtensions, pagePaths: rootPaths, pagesType: 'root', + pagesDir: pagesDir, }) } @@ -1256,7 +1266,7 @@ export default async function build( : appPaths?.find((p) => p.startsWith(actualPage + '/page.')) const pageRuntime = - pageType === 'pages' && pagePath + pagesDir && pageType === 'pages' && pagePath ? ( await getPageStaticInfo({ pageFilePath: join(pagesDir, pagePath), @@ -1913,6 +1923,7 @@ export default async function build( await staticWorkers.end() } : undefined, + appPaths, } const exportConfig: any = { ...config, diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index e5cdc0f93929..fa114b46ef03 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -305,7 +305,7 @@ export async function printTreeView( }: { distPath: string buildId: string - pagesDir: string + pagesDir?: string pageExtensions: string[] buildManifest: BuildManifest appBuildManifest?: AppBuildManifest @@ -345,7 +345,8 @@ export async function printTreeView( .replace(/(?:^|[.-])([0-9a-z]{6})[0-9a-z]{14}(?=\.)/, '.$1') // Check if we have a custom app. - const hasCustomApp = await findPageFile(pagesDir, '/_app', pageExtensions) + const hasCustomApp = + pagesDir && (await findPageFile(pagesDir, '/_app', pageExtensions, false)) const filterAndSortList = (list: ReadonlyArray) => list @@ -1081,7 +1082,13 @@ export async function isPageStatic({ getStaticProps: mod.getStaticProps, } } else { - componentsResult = await loadComponents(distDir, page, serverless) + componentsResult = await loadComponents( + distDir, + page, + serverless, + false, + false + ) } const Comp = componentsResult.Component @@ -1207,7 +1214,13 @@ export async function hasCustomGetInitialProps( ): Promise { require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig) - const components = await loadComponents(distDir, page, isLikeServerless) + const components = await loadComponents( + distDir, + page, + isLikeServerless, + false, + false + ) let mod = components.ComponentMod if (checkingApp) { @@ -1226,7 +1239,13 @@ export async function getNamedExports( runtimeEnvConfig: any ): Promise> { require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig) - const components = await loadComponents(distDir, page, isLikeServerless) + const components = await loadComponents( + distDir, + page, + isLikeServerless, + false, + false + ) let mod = components.ComponentMod return Object.keys(mod) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index d47806b40c23..02f1012f7497 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -518,7 +518,7 @@ export default async function getBaseWebpackConfig( entrypoints: webpack.EntryObject hasReactRoot: boolean isDevFallback?: boolean - pagesDir: string + pagesDir?: string reactProductionProfiling?: boolean rewrites: CustomRoutes['rewrites'] runWebpackSpan: Span @@ -794,24 +794,30 @@ export default async function getBaseWebpackConfig( if (dev) { customAppAliases[`${PAGES_DIR_ALIAS}/_app`] = [ - ...rawPageExtensions.reduce((prev, ext) => { - prev.push(path.join(pagesDir, `_app.${ext}`)) - return prev - }, [] as string[]), + ...(pagesDir + ? rawPageExtensions.reduce((prev, ext) => { + prev.push(path.join(pagesDir, `_app.${ext}`)) + return prev + }, [] as string[]) + : []), 'next/dist/pages/_app.js', ] customAppAliases[`${PAGES_DIR_ALIAS}/_error`] = [ - ...rawPageExtensions.reduce((prev, ext) => { - prev.push(path.join(pagesDir, `_error.${ext}`)) - return prev - }, [] as string[]), + ...(pagesDir + ? rawPageExtensions.reduce((prev, ext) => { + prev.push(path.join(pagesDir, `_error.${ext}`)) + return prev + }, [] as string[]) + : []), 'next/dist/pages/_error.js', ] customDocumentAliases[`${PAGES_DIR_ALIAS}/_document`] = [ - ...rawPageExtensions.reduce((prev, ext) => { - prev.push(path.join(pagesDir, `_document.${ext}`)) - return prev - }, [] as string[]), + ...(pagesDir + ? rawPageExtensions.reduce((prev, ext) => { + prev.push(path.join(pagesDir, `_document.${ext}`)) + return prev + }, [] as string[]) + : []), `next/dist/pages/_document.js`, ] } @@ -850,7 +856,7 @@ export default async function getBaseWebpackConfig( ...customDocumentAliases, ...customRootAliases, - [PAGES_DIR_ALIAS]: pagesDir, + ...(pagesDir ? { [PAGES_DIR_ALIAS]: pagesDir } : {}), ...(appDir ? { [APP_DIR_ALIAS]: appDir, @@ -2051,7 +2057,9 @@ export default async function getBaseWebpackConfig( webpackConfig = await buildConfiguration(webpackConfig, { supportedBrowsers, rootDirectory: dir, - customAppFile: new RegExp(escapeStringRegexp(path.join(pagesDir, `_app`))), + customAppFile: pagesDir + ? new RegExp(escapeStringRegexp(path.join(pagesDir, `_app`))) + : undefined, isDevelopment: dev, isServer: isNodeServer || isEdgeServer, isEdgeRuntime: isEdgeServer, diff --git a/packages/next/build/webpack/config/index.ts b/packages/next/build/webpack/config/index.ts index 8da928de6009..201403bae8c2 100644 --- a/packages/next/build/webpack/config/index.ts +++ b/packages/next/build/webpack/config/index.ts @@ -25,7 +25,7 @@ export async function build( }: { supportedBrowsers: string[] | undefined rootDirectory: string - customAppFile: RegExp + customAppFile: RegExp | undefined isDevelopment: boolean isServer: boolean isEdgeRuntime: boolean diff --git a/packages/next/build/webpack/config/utils.ts b/packages/next/build/webpack/config/utils.ts index d2cdbb1277aa..b8355ad52ef5 100644 --- a/packages/next/build/webpack/config/utils.ts +++ b/packages/next/build/webpack/config/utils.ts @@ -4,7 +4,7 @@ import type { NextConfigComplete } from '../../../server/config-shared' export type ConfigurationContext = { supportedBrowsers: string[] | undefined rootDirectory: string - customAppFile: RegExp + customAppFile: RegExp | undefined isDevelopment: boolean isProduction: boolean diff --git a/packages/next/build/webpack/plugins/react-loadable-plugin.ts b/packages/next/build/webpack/plugins/react-loadable-plugin.ts index aacb383b5dd0..2677a6c94658 100644 --- a/packages/next/build/webpack/plugins/react-loadable-plugin.ts +++ b/packages/next/build/webpack/plugins/react-loadable-plugin.ts @@ -53,9 +53,14 @@ function getChunkGroupFromBlock( function buildManifest( _compiler: webpack.Compiler, compilation: webpack.Compilation, - pagesDir: string, + pagesDir: string | undefined, dev: boolean ) { + // If there's no pagesDir, output an empty manifest + if (!pagesDir) { + return {} + } + let manifest: { [k: string]: { id: string | number; files: string[] } } = {} // This is allowed: @@ -145,13 +150,13 @@ function buildManifest( export class ReactLoadablePlugin { private filename: string - private pagesDir: string + private pagesDir?: string private runtimeAsset?: string private dev: boolean constructor(opts: { filename: string - pagesDir: string + pagesDir?: string runtimeAsset?: string dev: boolean }) { diff --git a/packages/next/cli/next-lint.ts b/packages/next/cli/next-lint.ts index c25b340e238d..ca0d1e5246a0 100755 --- a/packages/next/cli/next-lint.ts +++ b/packages/next/cli/next-lint.ts @@ -92,11 +92,11 @@ const nextLint: cliCommand = async (argv) => { printAndExit( ` Description - Run ESLint on every file in specified directories. + Run ESLint on every file in specified directories. If not configured, ESLint will be set up for the first time. Usage - $ next lint [options] + $ next lint [options] represents the directory of the Next.js application. If no directory is provided, the current directory will be used. @@ -127,7 +127,7 @@ const nextLint: cliCommand = async (argv) => { Handling warnings: --quiet Report errors only - default: false --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1 - + Output: -o, --output-file path::String Specify file to write report to -f, --format String Use a specific output format - default: Next.js custom formatter @@ -140,7 +140,7 @@ const nextLint: cliCommand = async (argv) => { --no-cache Disable caching --cache-location path::String Path to the cache file or directory - default: .eslintcache --cache-strategy String Strategy to use for detecting changed files in the cache, either metadata or content - default: metadata - + Miscellaneous: --error-on-unmatched-pattern Show errors when any file patterns are unmatched - default: false `, @@ -179,17 +179,16 @@ const nextLint: cliCommand = async (argv) => { const distDir = join(baseDir, nextConfig.distDir) const defaultCacheLocation = join(distDir, 'cache', 'eslint/') - runLintCheck( - baseDir, - pathsToLint, - false, - eslintOptions(args, defaultCacheLocation), - reportErrorsOnly, + runLintCheck(baseDir, pathsToLint, { + lintDuringBuild: false, + eslintOptions: eslintOptions(args, defaultCacheLocation), + reportErrorsOnly: reportErrorsOnly, maxWarnings, formatter, outputFile, - strict - ) + strict, + hasAppDir: !!nextConfig.experimental.appDir, + }) .then(async (lintResults) => { const lintOutput = typeof lintResults === 'string' ? lintResults : lintResults?.output diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index da2854199513..e5c56a41e6ae 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -133,6 +133,7 @@ interface ExportOptions { statusMessage?: string exportPageWorker?: typeof import('./worker').default endWorker?: () => Promise + appPaths?: string[] } export default async function exportApp( @@ -582,7 +583,6 @@ export default async function exportApp( outDir, pagesDataDir, renderOpts, - appDir: nextConfig.experimental.appDir, serverRuntimeConfig, subFolders, buildExport: options.buildExport, @@ -594,6 +594,7 @@ export default async function exportApp( parentSpanId: pageExportSpan.id, httpAgentOptions: nextConfig.httpAgentOptions, serverComponents: nextConfig.experimental.serverComponents, + appPaths: options.appPaths || [], }) for (const validation of result.ampValidations || []) { diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index 298b87cb5fc6..51655364d667 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -65,7 +65,7 @@ interface ExportPageInput { parentSpanId: any httpAgentOptions: NextConfigComplete['httpAgentOptions'] serverComponents?: boolean - appDir?: boolean + appPaths: string[] } interface ExportPageResults { @@ -91,7 +91,6 @@ interface RenderOpts { defaultLocale?: string domainLocales?: DomainLocale[] trailingSlash?: boolean - appDir?: boolean } type ComponentModule = ComponentType<{}> & { @@ -105,7 +104,7 @@ export default async function exportPage({ pathMap, distDir, outDir, - appDir, + appPaths, pagesDataDir, renderOpts, buildExport, @@ -273,6 +272,9 @@ export default async function exportPage({ return !buildExport && getStaticProps && !isDynamicRoute(path) } + const isAppPath = appPaths.some((appPath: string) => + appPath.startsWith(page + '.page') + ) if (serverless) { const curUrl = url.parse(req.url!, true) req.url = url.format({ @@ -292,8 +294,8 @@ export default async function exportPage({ distDir, page, serverless, - serverComponents, - appDir + !!serverComponents, + isAppPath ) const ampState = { ampFirst: pageConfig?.amp === true, @@ -359,7 +361,8 @@ export default async function exportPage({ distDir, page, serverless, - serverComponents + !!serverComponents, + isAppPath ) const ampState = { ampFirst: components.pageConfig?.amp === true, diff --git a/packages/next/lib/eslint/runLintCheck.ts b/packages/next/lib/eslint/runLintCheck.ts index 8f349ac2ed55..9799f6733074 100644 --- a/packages/next/lib/eslint/runLintCheck.ts +++ b/packages/next/lib/eslint/runLintCheck.ts @@ -83,12 +83,22 @@ async function lint( lintDirs: string[], eslintrcFile: string | null, pkgJsonPath: string | null, - lintDuringBuild: boolean = false, - eslintOptions: any = null, - reportErrorsOnly: boolean = false, - maxWarnings: number = -1, - formatter: string | null = null, - outputFile: string | null = null + hasAppDir: boolean, + { + lintDuringBuild = false, + eslintOptions = null, + reportErrorsOnly = false, + maxWarnings = -1, + formatter = null, + outputFile = null, + }: { + lintDuringBuild: boolean + eslintOptions: any + reportErrorsOnly: boolean + maxWarnings: number + formatter: string | null + outputFile: string | null + } ): Promise< | string | null @@ -176,8 +186,7 @@ async function lint( } } - // TODO: should we apply these rules to "root" dir as well? - const pagesDir = findPagesDir(baseDir).pages + const pagesDir = findPagesDir(baseDir, hasAppDir).pages if (nextEslintPluginIsEnabled) { let updatedPagesDir = false @@ -268,14 +277,27 @@ async function lint( export async function runLintCheck( baseDir: string, lintDirs: string[], - lintDuringBuild: boolean = false, - eslintOptions: any = null, - reportErrorsOnly: boolean = false, - maxWarnings: number = -1, - formatter: string | null = null, - outputFile: string | null = null, - strict: boolean = false + opts: { + lintDuringBuild?: boolean + eslintOptions?: any + reportErrorsOnly?: boolean + maxWarnings?: number + formatter?: string | null + outputFile?: string | null + strict?: boolean + hasAppDir: boolean + } ): ReturnType { + const { + lintDuringBuild = false, + eslintOptions = null, + reportErrorsOnly = false, + maxWarnings = -1, + formatter = null, + outputFile = null, + strict = false, + hasAppDir, + } = opts try { // Find user's .eslintrc file // See: https://eslint.org/docs/user-guide/configuring/configuration-files#configuration-file-formats @@ -313,12 +335,15 @@ export async function runLintCheck( lintDirs, eslintrcFile, pkgJsonPath, - lintDuringBuild, - eslintOptions, - reportErrorsOnly, - maxWarnings, - formatter, - outputFile + hasAppDir, + { + lintDuringBuild, + eslintOptions, + reportErrorsOnly, + maxWarnings, + formatter, + outputFile, + } ) } else { // Display warning if no ESLint configuration is present inside diff --git a/packages/next/lib/find-pages-dir.ts b/packages/next/lib/find-pages-dir.ts index 61592f9e4f9d..2edec19c3f99 100644 --- a/packages/next/lib/find-pages-dir.ts +++ b/packages/next/lib/find-pages-dir.ts @@ -24,19 +24,25 @@ function findDir(dir: string, name: 'pages' | 'app'): string | null { export function findPagesDir( dir: string, appDirEnabled?: boolean -): { pages: string; appDir?: string } { - const pagesDir = findDir(dir, 'pages') +): { pages: string | undefined; appDir: string | undefined } { + const pagesDir = findDir(dir, 'pages') || undefined let appDir: undefined | string if (appDirEnabled) { appDir = findDir(dir, 'app') || undefined + if (appDirEnabled == null && pagesDir == null) { + throw new Error( + "> Couldn't find any `pages` or `app` directory. Please create one under the project root" + ) + } } - // TODO: allow "root" dir without pages dir - if (pagesDir === null) { - throw new Error( - "> Couldn't find a `pages` directory. Please create one under the project root" - ) + if (!appDirEnabled) { + if (pagesDir == null) { + throw new Error( + "> Couldn't find a `pages` directory. Please create one under the project root" + ) + } } return { diff --git a/packages/next/lib/verifyAndLint.ts b/packages/next/lib/verifyAndLint.ts index 76f384db10de..b5e47d423f82 100644 --- a/packages/next/lib/verifyAndLint.ts +++ b/packages/next/lib/verifyAndLint.ts @@ -14,7 +14,8 @@ export async function verifyAndLint( configLintDirs: string[] | undefined, numWorkers: number | undefined, enableWorkerThreads: boolean | undefined, - telemetry: Telemetry + telemetry: Telemetry, + hasAppDir: boolean ): Promise { try { const lintWorkers = new Worker(require.resolve('./eslint/runLintCheck'), { @@ -38,8 +39,12 @@ export async function verifyAndLint( [] ) - const lintResults = await lintWorkers.runLintCheck(dir, lintDirs, true, { - cacheLocation, + const lintResults = await lintWorkers.runLintCheck(dir, lintDirs, { + hasAppDir, + lintDuringBuild: true, + eslintOptions: { + cacheLocation, + }, }) const lintOutput = typeof lintResults === 'string' ? lintResults : lintResults?.output diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index c162054dc1eb..29b732fcd3a1 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -241,9 +241,9 @@ export default abstract class Server { protected abstract getFilesystemPaths(): Set protected abstract findPageComponents( pathname: string, - query?: NextParsedUrlQuery, - params?: Params, - isAppDir?: boolean + query: NextParsedUrlQuery, + params: Params, + isAppPath: boolean ): Promise protected abstract getFontManifest(): FontManifest | undefined protected abstract getPrerenderManifest(): PrerenderManifest @@ -1522,9 +1522,9 @@ export default abstract class Server { bubbleNoFallback: boolean ) { const { query, pathname } = ctx - const appPath = this.getOriginalAppPath(pathname) let page = pathname + const appPath = this.getOriginalAppPath(pathname) if (typeof appPath === 'string') { page = appPath } @@ -1532,7 +1532,7 @@ export default abstract class Server { const result = await this.findPageComponents( page, query, - ctx.renderOpts.params, + ctx.renderOpts.params || {}, typeof appPath === 'string' ) if (result) { @@ -1737,7 +1737,7 @@ export default abstract class Server { // use static 404 page if available and is 404 response if (is404 && (await this.hasPage('/404'))) { - result = await this.findPageComponents('/404', query) + result = await this.findPageComponents('/404', query, {}, false) using404Page = result !== null } let statusPage = `/${res.statusCode}` @@ -1750,12 +1750,12 @@ export default abstract class Server { // skip ensuring /500 in dev mode as it isn't used and the // dev overlay is used instead if (statusPage !== '/500' || !this.renderOpts.dev) { - result = await this.findPageComponents(statusPage, query) + result = await this.findPageComponents(statusPage, query, {}, false) } } if (!result) { - result = await this.findPageComponents('/_error', query) + result = await this.findPageComponents('/_error', query, {}, false) statusPage = '/_error' } diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 76aa45105996..921706767ce1 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -159,7 +159,7 @@ export default class HotReloader { private dir: string private buildId: string private interceptors: any[] - private pagesDir: string + private pagesDir?: string private distDir: string private webpackHotMiddleware?: WebpackHotMiddleware private config: NextConfigComplete @@ -197,7 +197,7 @@ export default class HotReloader { appDir, }: { config: NextConfigComplete - pagesDir: string + pagesDir?: string distDir: string buildId: string previewProps: __ApiPreviewProps @@ -405,14 +405,21 @@ export default class HotReloader { : this.config.pageExtensions return webpackConfigSpan.traceAsyncFn(async () => { - const pagePaths = await webpackConfigSpan - .traceChild('get-page-paths') - .traceAsyncFn(() => - Promise.all([ - findPageFile(this.pagesDir, '/_app', rawPageExtensions), - findPageFile(this.pagesDir, '/_document', rawPageExtensions), - ]) - ) + const pagePaths = !this.pagesDir + ? ([] as (string | null)[]) + : await webpackConfigSpan + .traceChild('get-page-paths') + .traceAsyncFn(() => + Promise.all([ + findPageFile(this.pagesDir!, '/_app', rawPageExtensions, false), + findPageFile( + this.pagesDir!, + '/_document', + rawPageExtensions, + false + ), + ]) + ) this.pagesMapping = webpackConfigSpan .traceChild('create-pages-mapping') @@ -423,8 +430,9 @@ export default class HotReloader { pageExtensions: this.config.pageExtensions, pagesType: 'pages', pagePaths: pagePaths.filter( - (i): i is string => typeof i === 'string' + (i: string | null): i is string => typeof i === 'string' ), + pagesDir: this.pagesDir, }) ) @@ -848,6 +856,10 @@ export default class HotReloader { this.serverError = null this.serverStats = stats + if (!this.pagesDir) { + return + } + const { compilation } = stats // We only watch `_document` for changes on the server compilation @@ -1060,10 +1072,7 @@ export default class HotReloader { ) } - public async ensurePage( - page: string, - clientOnly: boolean = false - ): Promise { + public async ensurePage(page: string, clientOnly: boolean): Promise { // Make sure we don't re-build or dispose prebuilt pages if (page !== '/_error' && BLOCKED_PAGES.indexOf(page) !== -1) { return diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 0452eea5b6a2..78f4656d8532 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -102,7 +102,7 @@ export default class DevServer extends Server { private isCustomServer: boolean protected sortedRoutes?: string[] private addedUpgradeListener = false - private pagesDir: string + private pagesDir?: string private appDir?: string private actualMiddlewareFile?: string private middleware?: MiddlewareRoutingItem @@ -259,28 +259,32 @@ export default class DevServer extends Server { let resolved = false return new Promise(async (resolve, reject) => { - // Watchpack doesn't emit an event for an empty directory - fs.readdir(this.pagesDir, (_, files) => { - if (files?.length) { - return - } + if (this.pagesDir) { + // Watchpack doesn't emit an event for an empty directory + fs.readdir(this.pagesDir, (_, files) => { + if (files?.length) { + return + } - if (!resolved) { - resolve() - resolved = true - } - }) + if (!resolved) { + resolve() + resolved = true + } + }) + } const wp = (this.webpackWatcher = new Watchpack({ ignored: /([/\\]node_modules[/\\]|[/\\]\.next[/\\]|[/\\]\.git[/\\])/, })) - const pages = [this.pagesDir] + const pages = this.pagesDir ? [this.pagesDir] : [] const app = this.appDir ? [this.appDir] : [] const directories = [...pages, ...app] - const files = getPossibleMiddlewareFilenames( - pathJoin(this.pagesDir, '..'), - this.nextConfig.pageExtensions - ) + const files = this.pagesDir + ? getPossibleMiddlewareFilenames( + pathJoin(this.pagesDir, '..'), + this.nextConfig.pageExtensions + ) + : [] let nestedMiddleware: string[] = [] const envFiles = [ @@ -380,7 +384,7 @@ export default class DevServer extends Server { } let pageName = absolutePathToPage(fileName, { - pagesDir: isAppPath ? this.appDir! : this.pagesDir, + pagesDir: isAppPath ? this.appDir! : this.pagesDir!, extensions: this.nextConfig.pageExtensions, keepIndex: isAppPath, }) @@ -529,8 +533,11 @@ export default class DevServer extends Server { if (nestedMiddleware.length > 0) { Log.error( - new NestedMiddlewareError(nestedMiddleware, this.dir, this.pagesDir) - .message + new NestedMiddlewareError( + nestedMiddleware, + this.dir, + this.pagesDir! + ).message ) nestedMiddleware = [] } @@ -618,7 +625,7 @@ export default class DevServer extends Server { this.verifyingTypeScript = true const verifyResult = await verifyTypeScriptSetup({ dir: this.dir, - intentDirs: [this.pagesDir!, this.appDir].filter(Boolean) as string[], + intentDirs: [this.pagesDir, this.appDir].filter(Boolean) as string[], typeCheckPreflight: false, tsconfigPath: this.nextConfig.typescript.tsconfigPath, disableStaticImages: this.nextConfig.images.disableStaticImages, @@ -679,7 +686,10 @@ export default class DevServer extends Server { eventCliSession(this.distDir, this.nextConfig, { webpackVersion: 5, cliCommand: 'dev', - isSrcDir: relative(this.dir, this.pagesDir).startsWith('src'), + isSrcDir: + (!!this.pagesDir && + relative(this.dir, this.pagesDir).startsWith('src')) || + (!!this.appDir && relative(this.dir, this.appDir).startsWith('src')), hasNowJson: !!(await findUp('now.json', { cwd: this.dir })), isCustomServer: this.isCustomServer, }) @@ -721,7 +731,8 @@ export default class DevServer extends Server { return findPageFile( this.dir, normalizedPath, - this.nextConfig.pageExtensions + this.nextConfig.pageExtensions, + false ).then(Boolean) } @@ -730,17 +741,22 @@ export default class DevServer extends Server { const pageFile = await findPageFile( this.appDir, normalizedPath, - this.nextConfig.pageExtensions + this.nextConfig.pageExtensions, + true ) if (pageFile) return true } - const pageFile = await findPageFile( - this.pagesDir, - normalizedPath, - this.nextConfig.pageExtensions - ) - return !!pageFile + if (this.pagesDir) { + const pageFile = await findPageFile( + this.pagesDir, + normalizedPath, + this.nextConfig.pageExtensions, + false + ) + return !!pageFile + } + return false } protected async _beforeCatchAllRender( @@ -886,6 +902,7 @@ export default class DevServer extends Server { query: ParsedUrlQuery params: Params | undefined page: string + isAppPath: boolean }) { try { return super.runEdgeFunction({ @@ -1101,11 +1118,11 @@ export default class DevServer extends Server { } protected async ensureMiddleware() { - return this.hotReloader!.ensurePage(this.actualMiddlewareFile!) + return this.hotReloader!.ensurePage(this.actualMiddlewareFile!, false) } protected async ensureEdgeFunction(pathname: string) { - return this.hotReloader!.ensurePage(pathname) + return this.hotReloader!.ensurePage(pathname, false) } generateRoutes() { @@ -1271,14 +1288,14 @@ export default class DevServer extends Server { } protected async ensureApiPage(pathname: string): Promise { - return this.hotReloader!.ensurePage(pathname) + return this.hotReloader!.ensurePage(pathname, false) } protected async findPageComponents( pathname: string, - query: ParsedUrlQuery = {}, - params: Params | null = null, - isAppDir: boolean = false + query: ParsedUrlQuery, + params: Params, + isAppPath: boolean ): Promise { await this.devReady const compilationErr = await this.getCompilationError(pathname) @@ -1287,7 +1304,7 @@ export default class DevServer extends Server { throw new WrappedBuildError(compilationErr) } try { - await this.hotReloader!.ensurePage(pathname) + await this.hotReloader!.ensurePage(pathname, false) const serverComponents = this.nextConfig.experimental.serverComponents @@ -1298,7 +1315,7 @@ export default class DevServer extends Server { this.serverCSSManifest = super.getServerCSSManifest() } - return super.findPageComponents(pathname, query, params, isAppDir) + return super.findPageComponents(pathname, query, params, isAppPath) } catch (err) { if ((err as any).code !== 'ENOENT') { throw err @@ -1311,7 +1328,7 @@ export default class DevServer extends Server { await this.hotReloader!.buildFallbackError() // Build the error page to ensure the fallback is built too. // TODO: See if this can be moved into hotReloader or removed. - await this.hotReloader!.ensurePage('/_error') + await this.hotReloader!.ensurePage('/_error', false) return await loadDefaultErrorComponents(this.distDir) } diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts index 09570760812f..c3ce13b67ac6 100644 --- a/packages/next/server/dev/on-demand-entry-handler.ts +++ b/packages/next/server/dev/on-demand-entry-handler.ts @@ -255,6 +255,7 @@ function disposeInactiveEntries(maxInactiveAge: number) { }) } +// Normalize both app paths and page paths function tryToNormalizePagePath(page: string) { try { return normalizePagePath(page) @@ -276,16 +277,21 @@ function tryToNormalizePagePath(page: string) { */ async function findPagePathData( rootDir: string, - pagesDir: string, page: string, extensions: string[], + pagesDir?: string, appDir?: string ) { const normalizedPagePath = tryToNormalizePagePath(page) let pagePath: string | null = null if (isMiddlewareFile(normalizedPagePath)) { - pagePath = await findPageFile(rootDir, normalizedPagePath, extensions) + pagePath = await findPageFile( + rootDir, + normalizedPagePath, + extensions, + false + ) if (!pagePath) { throw new PageNotFoundError(normalizedPagePath) @@ -306,7 +312,7 @@ async function findPagePathData( // Check appDir first falling back to pagesDir if (appDir) { - pagePath = await findPageFile(appDir, normalizedPagePath, extensions) + pagePath = await findPageFile(appDir, normalizedPagePath, extensions, true) if (pagePath) { const pageUrl = ensureLeadingSlash( removePagePathTail(normalizePathSep(pagePath), { @@ -323,11 +329,16 @@ async function findPagePathData( } } - if (!pagePath) { - pagePath = await findPageFile(pagesDir, normalizedPagePath, extensions) + if (!pagePath && pagesDir) { + pagePath = await findPageFile( + pagesDir, + normalizedPagePath, + extensions, + false + ) } - if (pagePath !== null) { + if (pagePath !== null && pagesDir) { const pageUrl = ensureLeadingSlash( removePagePathTail(normalizePathSep(pagePath), { extensions, @@ -365,7 +376,7 @@ export function onDemandEntryHandler({ multiCompiler: webpack.MultiCompiler nextConfig: NextConfigComplete pagesBufferLength: number - pagesDir: string + pagesDir?: string rootDir: string appDir?: string }) { @@ -545,9 +556,9 @@ export function onDemandEntryHandler({ try { const pagePathData = await findPagePathData( rootDir, - pagesDir, page, nextConfig.pageExtensions, + pagesDir, appDir ) diff --git a/packages/next/server/dev/static-paths-worker.ts b/packages/next/server/dev/static-paths-worker.ts index 240178e25968..185948d7f2d1 100644 --- a/packages/next/server/dev/static-paths-worker.ts +++ b/packages/next/server/dev/static-paths-worker.ts @@ -38,7 +38,13 @@ export async function loadStaticPaths( require('../../shared/lib/runtime-config').setConfig(config) setHttpAgentOptions(httpAgentOptions) - const components = await loadComponents(distDir, pathname, serverless) + const components = await loadComponents( + distDir, + pathname, + serverless, + false, + false + ) if (!components.getStaticPaths) { // we shouldn't get to this point since the worker should diff --git a/packages/next/server/lib/find-page-file.ts b/packages/next/server/lib/find-page-file.ts index eab4d18f72cc..53d2a86dd35e 100644 --- a/packages/next/server/lib/find-page-file.ts +++ b/packages/next/server/lib/find-page-file.ts @@ -29,14 +29,16 @@ async function isTrueCasePagePath(pagePath: string, pagesDir: string) { export async function findPageFile( pagesDir: string, normalizedPagePath: string, - pageExtensions: string[] + pageExtensions: string[], + isAppDir: boolean ): Promise { - const pagePaths = getPagePaths(normalizedPagePath, pageExtensions) + const pagePaths = getPagePaths(normalizedPagePath, pageExtensions, isAppDir) const [existingPath, ...others] = ( await Promise.all( - pagePaths.map(async (path) => - (await fileExists(join(pagesDir, path))) ? path : null - ) + pagePaths.map(async (path) => { + const filePath = join(pagesDir, path) + return (await fileExists(filePath)) ? path : null + }) ) ).filter(nonNullable) diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index e54139ce0519..5cbf543ccc40 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -15,7 +15,7 @@ import { FLIGHT_MANIFEST, } from '../shared/lib/constants' import { join } from 'path' -import { requirePage, getPagePath } from './require' +import { requirePage } from './require' import { BuildManifest } from './get-page-files' import { interopDefault } from '../lib/interop-default' @@ -63,8 +63,8 @@ export async function loadComponents( distDir: string, pathname: string, serverless: boolean, - hasServerComponents?: boolean, - appDirEnabled?: boolean + hasServerComponents: boolean, + isAppPath: boolean ): Promise { if (serverless) { const ComponentMod = await requirePage(pathname, distDir, serverless) @@ -99,17 +99,21 @@ export async function loadComponents( } as LoadComponentsReturnType } - const [DocumentMod, AppMod, ComponentMod] = await Promise.all([ - Promise.resolve().then(() => - requirePage('/_document', distDir, serverless, appDirEnabled) - ), - Promise.resolve().then(() => - requirePage('/_app', distDir, serverless, appDirEnabled) - ), - Promise.resolve().then(() => - requirePage(pathname, distDir, serverless, appDirEnabled) - ), - ]) + let DocumentMod = {} + let AppMod = {} + if (!isAppPath) { + ;[DocumentMod, AppMod] = await Promise.all([ + Promise.resolve().then(() => + requirePage('/_document', distDir, serverless, false) + ), + Promise.resolve().then(() => + requirePage('/_app', distDir, serverless, false) + ), + ]) + } + const ComponentMod = await Promise.resolve().then(() => + requirePage(pathname, distDir, serverless, isAppPath) + ) const [buildManifest, reactLoadableManifest, serverComponentManifest] = await Promise.all([ @@ -126,20 +130,6 @@ export async function loadComponents( const { getServerSideProps, getStaticProps, getStaticPaths } = ComponentMod - let isAppPath = false - - if (appDirEnabled) { - const pagePath = getPagePath( - pathname, - distDir, - serverless, - false, - undefined, - appDirEnabled - ) - isAppPath = !!pagePath?.match(/server[/\\]app[/\\]/) - } - return { App, Document, diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 48f19bf2e474..5e39ac360fc7 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -101,6 +101,7 @@ import ResponseCache from './response-cache' import { IncrementalCache } from './lib/incremental-cache' import { interpolateDynamicPath } from '../build/webpack/loaders/next-serverless-loader/utils' import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex' +import { normalizeAppPath } from '../shared/lib/router/utils/app-paths' if (shouldUseReactRoot) { ;(process.env as any).__NEXT_REACT_ROOT = 'true' @@ -247,12 +248,20 @@ export default class NextNodeServer extends BaseServer { if (!options.dev) { // pre-warm _document and _app as these will be // needed for most requests - loadComponents(this.distDir, '/_document', this._isLikeServerless).catch( - () => {} - ) - loadComponents(this.distDir, '/_app', this._isLikeServerless).catch( - () => {} - ) + loadComponents( + this.distDir, + '/_document', + this._isLikeServerless, + false, + false + ).catch(() => {}) + loadComponents( + this.distDir, + '/_app', + this._isLikeServerless, + false, + false + ).catch(() => {}) } } @@ -750,6 +759,7 @@ export default class NextNodeServer extends BaseServer { query, params, page, + isAppPath: false, }) if (handledAsEdgeFunction) { @@ -879,10 +889,11 @@ export default class NextNodeServer extends BaseServer { ctx: RequestContext, bubbleNoFallback: boolean ) { - const appPath = this.getOriginalAppPath(ctx.pathname) + const appPath = this.getOriginalAppPath(ctx.pathname) || undefined let page = ctx.pathname - if (typeof appPath === 'string') { + const isAppPath = typeof appPath === 'string' + if (isAppPath) { page = appPath } @@ -895,7 +906,9 @@ export default class NextNodeServer extends BaseServer { res: ctx.res, query: ctx.query, params: ctx.renderOpts.params, - page, + page: ctx.pathname, + appPath, + isAppPath: isAppPath, }) return null } @@ -906,13 +919,17 @@ export default class NextNodeServer extends BaseServer { protected async findPageComponents( pathname: string, - query: NextParsedUrlQuery = {}, - params: Params | null = null, - isAppDir: boolean = false + query: NextParsedUrlQuery, + params: Params, + isAppPath: boolean ): Promise { let paths = [ // try serving a static AMP version first - query.amp ? normalizePagePath(pathname) + '.amp' : null, + query.amp + ? (isAppPath + ? normalizeAppPath(pathname) + : normalizePagePath(pathname)) + '.amp' + : null, pathname, ].filter(Boolean) @@ -931,8 +948,8 @@ export default class NextNodeServer extends BaseServer { this.distDir, pagePath!, !this.renderOpts.dev && this._isLikeServerless, - this.renderOpts.serverComponents, - this.nextConfig.experimental.appDir + !!this.renderOpts.serverComponents, + isAppPath ) if ( @@ -958,7 +975,7 @@ export default class NextNodeServer extends BaseServer { } as NextParsedUrlQuery) : query), // For appDir params is excluded. - ...((isAppDir ? {} : params) || {}), + ...((isAppPath ? {} : params) || {}), }, } } catch (err) { @@ -1999,18 +2016,17 @@ export default class NextNodeServer extends BaseServer { query: ParsedUrlQuery params: Params | undefined page: string + isAppPath: boolean + appPath?: string onWarning?: (warning: Error) => void }): Promise { let middlewareInfo: ReturnType | undefined - let appPath = this.getOriginalAppPath(params.page) - - if (typeof appPath === 'string') { - params.page = appPath - } - await this.ensureEdgeFunction(params.page) + // If it's edge app route, use appPath to find the edge SSR page + const page = params.isAppPath ? params.appPath! : params.page + await this.ensureEdgeFunction(page) middlewareInfo = this.getEdgeFunctionInfo({ - page: params.page, + page: page, middleware: false, }) @@ -2024,6 +2040,7 @@ export default class NextNodeServer extends BaseServer { Object.assign({}, getRequestMeta(params.req, '__NEXT_INIT_QUERY') || {}) ).toString() const locale = params.query.__nextLocale + // Use original pathname (without `/page`) instead of appPath for url let normalizedPathname = params.page if (isDataReq) { diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index 7afff576aea2..0b966acb9b8e 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -406,8 +406,9 @@ export default class NextWebServer extends BaseServer { } protected async findPageComponents( pathname: string, - query?: NextParsedUrlQuery, - params?: Params | null + query: NextParsedUrlQuery, + params: Params | null, + _isAppPath: boolean ) { const result = await this.serverOptions.webServerConfig.loadComponent( pathname diff --git a/packages/next/shared/lib/page-path/get-page-paths.ts b/packages/next/shared/lib/page-path/get-page-paths.ts index 4d4906e3d868..3ea3d9bd8e72 100644 --- a/packages/next/shared/lib/page-path/get-page-paths.ts +++ b/packages/next/shared/lib/page-path/get-page-paths.ts @@ -7,17 +7,28 @@ import { join } from '../isomorphic/path' * allowed extensions. This can be used to check which one of the files exists * and to debug inspected locations. * + * For pages, map `/route` to [`/route.[ext]`, `/route/index.[ext]`] + * For app paths, map `/route/page` to [`/route/page.[ext]`] + * * @param normalizedPagePath Normalized page path (it will denormalize). * @param extensions Allowed extensions. */ -export function getPagePaths(normalizedPagePath: string, extensions: string[]) { +export function getPagePaths( + normalizedPagePath: string, + extensions: string[], + isAppDir: boolean +) { const page = denormalizePagePath(normalizedPagePath) return flatten( extensions.map((extension) => { - return !normalizedPagePath.endsWith('/index') - ? [`${page}.${extension}`, join(page, `index.${extension}`)] - : [join(page, `index.${extension}`)] + const appPage = `${page}.${extension}` + const folderIndexPage = join(page, `index.${extension}`) + + if (!normalizedPagePath.endsWith('/index')) { + return isAppDir ? [appPage] : [`${page}.${extension}`, folderIndexPage] + } + return [isAppDir ? appPage : folderIndexPage] }) ) } diff --git a/test/e2e/app-dir/app-rendering/pages/.gitkeep b/test/e2e/app-dir/app-rendering/pages/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/e2e/app-dir/rendering.test.ts b/test/e2e/app-dir/rendering.test.ts index 10ec1b72df4f..69f1f0dd3859 100644 --- a/test/e2e/app-dir/rendering.test.ts +++ b/test/e2e/app-dir/rendering.test.ts @@ -22,7 +22,6 @@ describe('app dir rendering', () => { next = await createNext({ files: { app: new FileRef(path.join(__dirname, 'app-rendering/app')), - pages: new FileRef(path.join(__dirname, 'app-rendering/pages')), 'next.config.js': new FileRef( path.join(__dirname, 'app-rendering/next.config.js') ), diff --git a/test/e2e/app-dir/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic.test.ts index 09dd011d5229..4b9b3471992a 100644 --- a/test/e2e/app-dir/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic.test.ts @@ -39,7 +39,6 @@ describe('app dir - react server components', () => { next = await createNext({ files: { node_modules_bak: new FileRef(path.join(appDir, 'node_modules_bak')), - pages: new FileRef(path.join(appDir, 'pages')), public: new FileRef(path.join(appDir, 'public')), components: new FileRef(path.join(appDir, 'components')), app: new FileRef(path.join(appDir, 'app')), @@ -58,6 +57,7 @@ describe('app dir - react server components', () => { start: 'next start', }, }, + installCommand: 'yarn', startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start', buildCommand: 'yarn build', }) @@ -167,7 +167,7 @@ describe('app dir - react server components', () => { // expect(modFromClient[1]).not.toBe(modFromServer[1]) }) - it('should be able to navigate between rsc pages', async () => { + it('should be able to navigate between rsc routes', async () => { const browser = await webdriver(next.url, '/root') await browser.waitForElementByCss('#goto-next-link').click() @@ -262,6 +262,14 @@ describe('app dir - react server components', () => { expect(manipulated).toBe(undefined) }) + it('should render built-in 404 page for missing route if pagesDir is not presented', async () => { + const res = await fetchViaHTTP(next.url, '/does-not-exist') + + expect(res.status).toBe(404) + const html = await res.text() + expect(html).toContain('This page could not be found') + }) + it('should suspense next/image in server components', async () => { const imageHTML = await renderViaHTTP(next.url, '/next-api/image') const imageTag = getNodeBySelector(imageHTML, '#myimg') diff --git a/test/e2e/app-dir/rsc-basic/pages/.gitkeep b/test/e2e/app-dir/rsc-basic/pages/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/unit/find-page-file.test.ts b/test/unit/find-page-file.test.ts index 87c97d0319f2..b59853a41017 100644 --- a/test/unit/find-page-file.test.ts +++ b/test/unit/find-page-file.test.ts @@ -13,19 +13,34 @@ const dirWithPages = join(resolveDataDir, 'readdir', 'pages') describe('findPageFile', () => { it('should work', async () => { const pagePath = normalizePagePath('/nav/about') - const result = await findPageFile(dirWithPages, pagePath, ['jsx', 'js']) + const result = await findPageFile( + dirWithPages, + pagePath, + ['jsx', 'js'], + false + ) expect(result).toMatch(/^[\\/]nav[\\/]about\.js/) }) it('should work with nested index.js', async () => { const pagePath = normalizePagePath('/nested') - const result = await findPageFile(dirWithPages, pagePath, ['jsx', 'js']) + const result = await findPageFile( + dirWithPages, + pagePath, + ['jsx', 'js'], + false + ) expect(result).toMatch(/^[\\/]nested[\\/]index\.js/) }) it('should prefer prefered.js before preferred/index.js', async () => { const pagePath = normalizePagePath('/prefered') - const result = await findPageFile(dirWithPages, pagePath, ['jsx', 'js']) + const result = await findPageFile( + dirWithPages, + pagePath, + ['jsx', 'js'], + false + ) expect(result).toMatch(/^[\\/]prefered\.js/) }) })