Skip to content

Commit 93249ab

Browse files
authored
[16.3.X] Emit whole-app server NFTs when output: 'standalone' is used with an adapter (#98167)
Backports #97287
1 parent 6549fd7 commit 93249ab

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

‎crates/next-api/src/next_server_nft.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,24 @@ enum ServerNftType {
7474

7575
#[turbo_tasks::function]
7676
pub async fn next_server_nft_assets(project: Vc<Project>) -> Result<Vc<OutputAssets>> {
77-
if *project.next_config().is_using_adapter().await? {
77+
let is_standalone = *project.next_config().is_standalone().await?;
78+
79+
if *project.next_config().is_using_adapter().await? && !is_standalone {
7880
// When using an adapter, `next-server.js.nft.json` / `next-minimal-server.js.nft.json` are
7981
// not needed: they exist for `output: 'standalone'` (see `copyTracedFiles`), while an
8082
// adapter assembles the deployment from the per-endpoint NFTs in build-complete.ts. What
8183
// those two files trace on top of the endpoints - the `styled-jsx` modules the require hook
8284
// needs at runtime - is part of every endpoint's trace via
8385
// `Project::additional_traced_modules`, so nothing is lost here.
86+
//
87+
// The exception is `output: 'standalone'` configured alongside an adapter:
88+
// `copyTracedFiles` reads `next-server.js.nft.json` unconditionally whenever standalone
89+
// output is requested (adapter or not), so suppressing the pair crashes the build
90+
// (see #96646).
8491
return Ok(Vc::cell(vec![]));
8592
}
8693

8794
let has_next_support = *project.ci_has_next_support().await?;
88-
let is_standalone = *project.next_config().is_standalone().await?;
8995

9096
let minimal = ResolvedVc::upcast(
9197
ServerNftJsonAsset::new(project, ServerNftType::Minimal)

‎test/production/next-server-nft/next-server-nft.test.ts‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,5 +723,41 @@ async function readNormalizedNFT(next, name) {
723723
`)
724724
})
725725
})
726+
727+
describe('with adapters and output:standalone', () => {
728+
const { next, skipped } = nextTestSetup({
729+
files: __dirname,
730+
dependencies: {
731+
typescript: '5.9.2',
732+
},
733+
nextConfig: {
734+
output: 'standalone',
735+
adapterPath: path.join(__dirname, './my-adapter.mjs'),
736+
},
737+
})
738+
739+
if (skipped) {
740+
return
741+
}
742+
743+
// Regression test for #96646: with an adapter configured, the whole-app server NFTs were
744+
// suppressed while `copyTracedFiles` (which runs for `output: 'standalone'`, adapter or
745+
// not) still reads `next-server.js.nft.json` unconditionally — crashing the build with
746+
// ENOENT.
747+
it('should emit both whole-app server NFTs and complete the build', async () => {
748+
const serverTrace = await next.readJSON('.next/next-server.js.nft.json')
749+
expect(Array.isArray(serverTrace.files)).toBe(true)
750+
expect(serverTrace.files.length).toBeGreaterThan(0)
751+
752+
const minimalTrace = await next.readJSON(
753+
'.next/next-minimal-server.js.nft.json'
754+
)
755+
expect(Array.isArray(minimalTrace.files)).toBe(true)
756+
757+
// The adapter still ran (my-adapter.mjs writes build-complete.json).
758+
const buildComplete = await next.readJSON('build-complete.json')
759+
expect(buildComplete).toBeTruthy()
760+
})
761+
})
726762
}
727763
)

0 commit comments

Comments
 (0)