Repository: vercel/next.js
Pull request: [turbopack] set app dir only to true when no pages entries detected

* Do not always include the default pages entries `_app`, `_document`, `_error` for pages router, only enqueue these entries when there's more than one pages router entry.
* On turbopack JS side, when determine there's no pages router entries, set the `appDirOnly` flag to true as it's equal to "app only" mode and the rest logic will ensure no pages router entries are emitted.

Changed files:
filename: crates/next-api/src/project.rs
patch: @@ -848,12 +848,7 @@ impl Project {
         let mut endpoints = Vec::new();
 
         let entrypoints = self.entrypoints().await?;
-
-        // Always include these basic pages endpoints regardless of `app_dir_only`. The user's
-        // page routes themselves are excluded below.
-        endpoints.push(entrypoints.pages_error_endpoint);
-        endpoints.push(entrypoints.pages_app_endpoint);
-        endpoints.push(entrypoints.pages_document_endpoint);
+        let mut is_pages_entries_added = false;
 
         if let Some(middleware) = &entrypoints.middleware {
             endpoints.push(middleware.endpoint);
@@ -872,11 +867,23 @@ impl Project {
                 } => {
                     if !app_dir_only {
                         endpoints.push(*html_endpoint);
+                        if !is_pages_entries_added {
+                            endpoints.push(entrypoints.pages_error_endpoint);
+                            endpoints.push(entrypoints.pages_app_endpoint);
+                            endpoints.push(entrypoints.pages_document_endpoint);
+                            is_pages_entries_added = true;
+                        }
                     }
                 }
                 Route::PageApi { endpoint } => {
                     if !app_dir_only {
                         endpoints.push(*endpoint);
+                        if !is_pages_entries_added {
+                            endpoints.push(entrypoints.pages_error_endpoint);
+                            endpoints.push(entrypoints.pages_app_endpoint);
+                            endpoints.push(entrypoints.pages_document_endpoint);
+                            is_pages_entries_added = true;
+                        }
                     }
                 }
                 Route::AppPage(page_routes) => {

filename: packages/next/src/build/turbopack-build/impl.ts
patch: @@ -41,7 +41,6 @@ export async function turbopackBuild(): Promise<{
   const previewProps = NextBuildContext.previewProps!
   const hasRewrites = NextBuildContext.hasRewrites!
   const rewrites = NextBuildContext.rewrites!
-  const appDirOnly = NextBuildContext.appDirOnly!
   const noMangling = NextBuildContext.noMangling!
   const currentNodeJsVersion = process.versions.node
 
@@ -107,9 +106,23 @@ export async function turbopackBuild(): Promise<{
       '{"type": "commonjs"}'
     )
 
+    let appDirOnly = NextBuildContext.appDirOnly!
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     const entrypoints = await project.writeAllEntrypointsToDisk(appDirOnly)
 
+    const hasPagesEntries = Array.from(entrypoints.routes.values()).some(
+      (route) => {
+        if (route.type === 'page' || route.type === 'page-api') {
+          return true
+        }
+        return false
+      }
+    )
+    // If there's no pages entries, then we are in app-dir-only mode
+    if (!hasPagesEntries) {
+      appDirOnly = true
+    }
+
     const manifestLoader = new TurbopackManifestLoader({
       buildId,
       distDir,

filename: test/production/500-page/app-router-only/app-router-only.test.ts
patch: @@ -3,7 +3,7 @@ import fsp from 'fs/promises'
 import path from 'path'
 
 describe('500-page app-router-only', () => {
-  const { next, isNextStart, isTurbopack, isNextDeploy } = nextTestSetup({
+  const { next, isNextStart, isNextDeploy } = nextTestSetup({
     files: __dirname,
   })
 
@@ -48,17 +48,9 @@ describe('500-page app-router-only', () => {
       const pagesDir = path.join(next.testDir, '.next', 'server', 'pages')
       const files = await fsp.readdir(pagesDir)
       expect(files).not.toContain('500')
-      if (isTurbopack) {
-        // In turbopack, _app, _document, _error are still generated with manifest, but no javascript files.
-        // The manifest are under the folder, they're still needed for static generation.
-        expect(files).not.toContain('_app.js')
-        expect(files).not.toContain('_document.js')
-        expect(files).not.toContain('_error.js')
-      } else {
-        expect(files).not.toContain('_app')
-        expect(files).not.toContain('_document')
-        expect(files).not.toContain('_error')
-      }
+      expect(files).not.toContain('_app')
+      expect(files).not.toContain('_document')
+      expect(files).not.toContain('_error')
     })
   }
 })