Skip to content

Commit 2eaa3ae

Browse files
DuCanhGHijjk
andauthored
fix appDir returning 404 in production with "output": "standalone" (#43268)
<!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change that you're making: --> Fixes: #42812 Fixes: #43037 ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) This PR fixes the issue in which urls from appDir will always not be found in production when built with `"output": "standalone"` by copying .next/server/app and .next/server/app-paths-manifest.json into .next/standalone/server. Co-authored-by: JJ Kasper <[email protected]>
1 parent 60d5c96 commit 2eaa3ae

File tree

4 files changed

+109
-11
lines changed

4 files changed

+109
-11
lines changed

packages/next/build/index.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ import {
6161
FONT_LOADER_MANIFEST,
6262
CLIENT_STATIC_FILES_RUNTIME_MAIN_APP,
6363
APP_CLIENT_INTERNALS,
64+
SUBRESOURCE_INTEGRITY_MANIFEST,
65+
MIDDLEWARE_BUILD_MANIFEST,
66+
MIDDLEWARE_REACT_LOADABLE_MANIFEST,
6467
} from '../shared/lib/constants'
6568
import { getSortedRoutes, isDynamicRoute } from '../shared/lib/router/utils'
6669
import { __ApiPreviewProps } from '../server/api-utils'
@@ -865,6 +868,11 @@ export default async function build(
865868
)
866869

867870
const manifestPath = path.join(distDir, SERVER_DIRECTORY, PAGES_MANIFEST)
871+
const appManifestPath = path.join(
872+
distDir,
873+
SERVER_DIRECTORY,
874+
APP_PATHS_MANIFEST
875+
)
868876

869877
const requiredServerFiles = nextBuildSpan
870878
.traceChild('generate-required-server-files')
@@ -885,8 +893,26 @@ export default async function build(
885893
BUILD_MANIFEST,
886894
PRERENDER_MANIFEST,
887895
path.join(SERVER_DIRECTORY, MIDDLEWARE_MANIFEST),
896+
path.join(SERVER_DIRECTORY, MIDDLEWARE_BUILD_MANIFEST + '.js'),
897+
path.join(
898+
SERVER_DIRECTORY,
899+
MIDDLEWARE_REACT_LOADABLE_MANIFEST + '.js'
900+
),
888901
...(appDir
889902
? [
903+
...(config.experimental.sri
904+
? [
905+
path.join(
906+
SERVER_DIRECTORY,
907+
SUBRESOURCE_INTEGRITY_MANIFEST + '.js'
908+
),
909+
path.join(
910+
SERVER_DIRECTORY,
911+
SUBRESOURCE_INTEGRITY_MANIFEST + '.json'
912+
),
913+
]
914+
: []),
915+
path.relative(distDir, appManifestPath),
890916
path.join(SERVER_DIRECTORY, FLIGHT_MANIFEST + '.js'),
891917
path.join(SERVER_DIRECTORY, FLIGHT_MANIFEST + '.json'),
892918
path.join(
@@ -2046,6 +2072,7 @@ export default async function build(
20462072
dir,
20472073
distDir,
20482074
pageKeys.pages,
2075+
pageKeys.app,
20492076
outputFileTracingRoot,
20502077
requiredServerFiles.config,
20512078
middlewareManifest
@@ -2750,17 +2777,32 @@ export default async function build(
27502777
})
27512778
await promises.copyFile(filePath, outputPath)
27522779
}
2753-
await recursiveCopy(
2754-
path.join(distDir, SERVER_DIRECTORY, 'pages'),
2755-
path.join(
2756-
distDir,
2757-
'standalone',
2758-
path.relative(outputFileTracingRoot, distDir),
2759-
SERVER_DIRECTORY,
2760-
'pages'
2761-
),
2762-
{ overwrite: true }
2763-
)
2780+
if (pagesDir) {
2781+
await recursiveCopy(
2782+
path.join(distDir, SERVER_DIRECTORY, 'pages'),
2783+
path.join(
2784+
distDir,
2785+
'standalone',
2786+
path.relative(outputFileTracingRoot, distDir),
2787+
SERVER_DIRECTORY,
2788+
'pages'
2789+
),
2790+
{ overwrite: true }
2791+
)
2792+
}
2793+
if (appDir) {
2794+
await recursiveCopy(
2795+
path.join(distDir, SERVER_DIRECTORY, 'app'),
2796+
path.join(
2797+
distDir,
2798+
'standalone',
2799+
path.relative(outputFileTracingRoot, distDir),
2800+
SERVER_DIRECTORY,
2801+
'app'
2802+
),
2803+
{ overwrite: true }
2804+
)
2805+
}
27642806
}
27652807

27662808
staticPages.forEach((pg) => allStaticPages.add(pg))

packages/next/build/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,7 @@ export async function copyTracedFiles(
15981598
dir: string,
15991599
distDir: string,
16001600
pageKeys: ReadonlyArray<string>,
1601+
appPageKeys: readonly string[] | undefined,
16011602
tracingRoot: string,
16021603
serverConfig: { [key: string]: any },
16031604
middlewareManifest: MiddlewareManifest
@@ -1680,6 +1681,15 @@ export async function copyTracedFiles(
16801681
Log.warn(`Failed to copy traced files for ${pageFile}`, err)
16811682
})
16821683
}
1684+
if (appPageKeys) {
1685+
for (const page of appPageKeys) {
1686+
const pageFile = path.join(distDir, 'server', 'app', `${page}`, 'page.js')
1687+
const pageTraceFile = `${pageFile}.nft.json`
1688+
await handleTraceFiles(pageTraceFile).catch((err) => {
1689+
Log.warn(`Failed to copy traced files for ${pageFile}`, err)
1690+
})
1691+
}
1692+
}
16831693
await handleTraceFiles(path.join(distDir, 'next-server.js.nft.json'))
16841694
const serverOutputPath = path.join(
16851695
outputPath,

test/e2e/app-dir/app/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
algorithm: 'sha256',
66
},
77
},
8+
output: 'standalone',
89
rewrites: async () => {
910
return {
1011
afterFiles: [

test/e2e/app-dir/index.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
import os from 'os'
12
import { createNext, FileRef } from 'e2e-utils'
23
import crypto from 'crypto'
34
import { NextInstance } from 'test/lib/next-modes/base'
45
import {
56
check,
67
fetchViaHTTP,
8+
findPort,
79
getRedboxHeader,
810
hasRedbox,
11+
initNextServerScript,
12+
killApp,
913
renderViaHTTP,
1014
waitFor,
1115
} from 'next-test-utils'
1216
import path from 'path'
1317
import cheerio from 'cheerio'
1418
import webdriver from 'next-webdriver'
19+
import fs from 'fs-extra'
1520

1621
describe('app dir', () => {
1722
const isDev = (global as any).isNextDev
@@ -2554,4 +2559,44 @@ describe('app dir', () => {
25542559
}
25552560

25562561
runTests()
2562+
2563+
if ((global as any).isNextStart) {
2564+
it('should work correctly with output standalone', async () => {
2565+
const tmpFolder = path.join(os.tmpdir(), 'next-standalone-' + Date.now())
2566+
await fs.move(path.join(next.testDir, '.next/standalone'), tmpFolder)
2567+
let server
2568+
2569+
try {
2570+
const testServer = path.join(tmpFolder, 'server.js')
2571+
const appPort = await findPort()
2572+
server = await initNextServerScript(
2573+
testServer,
2574+
/Listening on/,
2575+
{
2576+
...process.env,
2577+
PORT: appPort,
2578+
},
2579+
undefined,
2580+
{
2581+
cwd: tmpFolder,
2582+
}
2583+
)
2584+
2585+
for (const testPath of [
2586+
'/',
2587+
'/api/hello',
2588+
'/blog/first',
2589+
'/dashboard',
2590+
'/dashboard/deployments/123',
2591+
'/catch-all/first',
2592+
]) {
2593+
const res = await fetchViaHTTP(appPort, testPath)
2594+
expect(res.status).toBe(200)
2595+
}
2596+
} finally {
2597+
if (server) await killApp(server)
2598+
await fs.remove(tmpFolder)
2599+
}
2600+
})
2601+
}
25572602
})

0 commit comments

Comments
 (0)