Environment
- Firebase App Hosting (managed Cloud Build pipeline; buildpack chain
ubuntu → preparer → google.nodejs.turborepo → build → google.nodejs.firebasebundle → publisher)
@apphosting/adapter-nextjs 14.0.21 (current latest)
- pnpm 9 + Turborepo monorepo (~70 workspace packages), Next.js 15.x,
output: 'standalone'
- 4 App Hosting backends built from the same monorepo
Problem
For monorepo builds, the Next.js adapter's generateBundleYaml skips emitting outputFiles.serverApp.include whenever MONOREPO_COMMAND is set:
// packages/@apphosting/adapter-nextjs/src/utils.ts
// TODO (b/432285470) See if there is a way to also delete files for apps using Nx monorepos
if (!process.env.MONOREPO_COMMAND) {
outputBundle.outputFiles = {
serverApp: {
include: [normalize(relative(cwd, opts.outputDirectoryAppPath))],
},
};
}
With no outputFiles allowlist in bundle.yaml, the google.nodejs.firebasebundle buildpack tars the entire /workspace into the image's app layer — even though runCommand targets the self-contained .next/standalone server that Next.js produced specifically so deployments can ship only the traced closure.
Measured impact (real production backends, 2026-07-19)
Every one of our four backends ships a 2,410–2,713 MB compressed image whose app layer (2,252–2,554 MB) uncompresses to ~6.3 GB. Inventory of one layer (a small, self-contained app in the workspace):
| Content |
Uncompressed |
Needed at runtime? |
node_modules/.pnpm (full workspace install, dev deps included) |
2,911 MB |
No |
.pnpm-store/v3 (pnpm download cache) |
2,687 MB |
No |
.next/standalone (the actual server, 109 traced packages) |
204 MB |
Yes — the only part needed |
.next/cache, .turbo/cache |
204 MB |
No |
other workspace apps' source, .git |
~260 MB |
No |
So ~94% of the layer is build-time material. The runtime needs ~204 MB; we ship ~2.5 GB, four times over (once per backend). Cost per deploy: the single Adding 1/1 app layer(s) export step takes 4–6 min and the registry push another 1.5–2 min — ~6–8 minutes of every deploy on every backend is spent exporting and pushing content the server never reads. It also inflates cold-start image pulls.
Notably, the layer even includes the pnpm store and .next/.turbo caches, which are pure build caches regardless of the monorepo question.
Expected behavior
Monorepo builds should get the same treatment single-app builds already get: bundle.yaml carries outputFiles.serverApp.include (per the output-bundle spec in firebase/apphosting-adapters) pointing at the app's output directory, and the bundle buildpack prunes accordingly. For Next.js standalone output the traced closure is self-contained, so at minimum the app's .next/standalone + .next/static + public would be a correct include set — our measured images would drop from ~2.5 GB to roughly 100–250 MB.
No user-side workaround exists
- There is no
apphosting.yaml / bundle.yaml knob to exclude or prune content.
- Overriding the gate via
apphosting.yaml env is impossible: the adapter's check is if (!process.env.MONOREPO_COMMAND), and App Hosting's config validation rejects value: "" (we tested — the build fails with "apphosting.yaml … is not formatted properly"), while any accepted value is truthy.
outputFileTracingExcludes is irrelevant — it only shrinks the 204 MB standalone trace, not the ~6 GB shipped around it.
- Redirecting the pnpm store out of
/workspace via .npmrc store-dir claws back ~950 MB compressed, but the remaining ~1.3 GB (workspace node_modules, caches, sibling apps, .git) is untouchable from user config.
Ask
The TODO already references internal bug b/432285470 — could this issue be linked to it, and monorepo pruning be prioritized? Happy to provide build IDs, full layer inventories, or test a canary adapter against our four-backend monorepo.
Environment
ubuntu → preparer → google.nodejs.turborepo → build → google.nodejs.firebasebundle → publisher)@apphosting/adapter-nextjs14.0.21 (currentlatest)output: 'standalone'Problem
For monorepo builds, the Next.js adapter's
generateBundleYamlskips emittingoutputFiles.serverApp.includewheneverMONOREPO_COMMANDis set:With no
outputFilesallowlist inbundle.yaml, thegoogle.nodejs.firebasebundlebuildpack tars the entire/workspaceinto the image's app layer — even thoughrunCommandtargets the self-contained.next/standaloneserver that Next.js produced specifically so deployments can ship only the traced closure.Measured impact (real production backends, 2026-07-19)
Every one of our four backends ships a 2,410–2,713 MB compressed image whose app layer (2,252–2,554 MB) uncompresses to ~6.3 GB. Inventory of one layer (a small, self-contained app in the workspace):
node_modules/.pnpm(full workspace install, dev deps included).pnpm-store/v3(pnpm download cache).next/standalone(the actual server, 109 traced packages).next/cache,.turbo/cache.gitSo ~94% of the layer is build-time material. The runtime needs ~204 MB; we ship ~2.5 GB, four times over (once per backend). Cost per deploy: the single
Adding 1/1 app layer(s)export step takes 4–6 min and the registry push another 1.5–2 min — ~6–8 minutes of every deploy on every backend is spent exporting and pushing content the server never reads. It also inflates cold-start image pulls.Notably, the layer even includes the pnpm store and
.next/.turbocaches, which are pure build caches regardless of the monorepo question.Expected behavior
Monorepo builds should get the same treatment single-app builds already get:
bundle.yamlcarriesoutputFiles.serverApp.include(per the output-bundle spec in firebase/apphosting-adapters) pointing at the app's output directory, and the bundle buildpack prunes accordingly. For Next.jsstandaloneoutput the traced closure is self-contained, so at minimum the app's.next/standalone+.next/static+publicwould be a correct include set — our measured images would drop from ~2.5 GB to roughly 100–250 MB.No user-side workaround exists
apphosting.yaml/bundle.yamlknob to exclude or prune content.apphosting.yamlenv is impossible: the adapter's check isif (!process.env.MONOREPO_COMMAND), and App Hosting's config validation rejectsvalue: ""(we tested — the build fails with "apphosting.yaml … is not formatted properly"), while any accepted value is truthy.outputFileTracingExcludesis irrelevant — it only shrinks the 204 MB standalone trace, not the ~6 GB shipped around it./workspacevia.npmrc store-dirclaws back ~950 MB compressed, but the remaining ~1.3 GB (workspacenode_modules, caches, sibling apps,.git) is untouchable from user config.Ask
The TODO already references internal bug b/432285470 — could this issue be linked to it, and monorepo pruning be prioritized? Happy to provide build IDs, full layer inventories, or test a canary adapter against our four-backend monorepo.