Bug Description
In monorepo (Turborepo) setups with Next.js, the copyResources function in packages/@apphosting/adapter-nextjs/src/utils.ts skips copying the full public/ directory from the app source into the standalone output because a partial public/ directory already exists from Next.js file tracing.
Steps to Reproduce
- Create a Turborepo monorepo with a Next.js app in
packages/web
- Place static assets in
packages/web/public/ (e.g., logo.svg, favicons)
- Reference these assets via string paths (
src="/logo.svg") rather than static imports
- Have at least one subdirectory in
public/ that IS traced by webpack (e.g., public/legal/ read via fs in server components)
- Deploy to Firebase App Hosting with
rootDirectory: packages/web
Expected Behavior
All files from packages/web/public/ should be present in the deployed container and served correctly.
Actual Behavior
Only files traced by Next.js standalone (e.g., public/legal/) are present. All other public assets (logo.svg, favicons, other SVGs) return 404.
Root Cause
In utils.ts, the copyResources function uses a top-level existence check:
const existsInOutputBundle = await exists(join(outputBundleAppDir, path));
if (!isbundleYamlDir && !existsInOutputBundle && !isApphostingYaml) {
await copy(join(appDir, path), join(outputBundleAppDir, path));
}
When Next.js standalone tracing creates a partial public/ directory (containing only traced files like legal/), exists() returns true for the public entry, causing the entire directory copy to be skipped. The function should merge directories rather than skip them.
Suggested Fix
Use fs-extra's copy with { overwrite: false } to merge directories without overwriting existing files:
// Instead of skipping if exists, merge directories
await copy(join(appDir, path), join(outputBundleAppDir, path), { overwrite: false });
Or check at the file level rather than the directory level.
Environment
@apphosting/adapter-nextjs: v14.0.21
- Next.js: 15.x
- Monorepo: Turborepo
- Root directory:
packages/web
- Node.js: 22.x
Bug Description
In monorepo (Turborepo) setups with Next.js, the
copyResourcesfunction inpackages/@apphosting/adapter-nextjs/src/utils.tsskips copying the fullpublic/directory from the app source into the standalone output because a partialpublic/directory already exists from Next.js file tracing.Steps to Reproduce
packages/webpackages/web/public/(e.g.,logo.svg, favicons)src="/logo.svg") rather than static importspublic/that IS traced by webpack (e.g.,public/legal/read viafsin server components)rootDirectory: packages/webExpected Behavior
All files from
packages/web/public/should be present in the deployed container and served correctly.Actual Behavior
Only files traced by Next.js standalone (e.g.,
public/legal/) are present. All other public assets (logo.svg, favicons, other SVGs) return 404.Root Cause
In
utils.ts, thecopyResourcesfunction uses a top-level existence check:When Next.js standalone tracing creates a partial
public/directory (containing only traced files likelegal/),exists()returnstruefor thepublicentry, causing the entire directory copy to be skipped. The function should merge directories rather than skip them.Suggested Fix
Use
fs-extra'scopywith{ overwrite: false }to merge directories without overwriting existing files:Or check at the file level rather than the directory level.
Environment
@apphosting/adapter-nextjs: v14.0.21packages/web