firebase / firebase/apphosting-adapters
copyResources skips merging public/ directory in monorepo standalone builds
- Dominant language
- TypeScript
- Stars
- 478
- Forks
- 1.5k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 2
Description
## 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
1. Create a Turborepo monorepo with a Next.js app in `packages/web`
2. Place static assets in `packages/web/public/` (e.g., `logo.svg`, favicons)
3. Reference these assets via string paths (`src="/logo.svg"`) rather than static imports
4. Have at least one subdirectory in `public/` that IS traced by webpack (e.g., `public/legal/` read via `fs` in server components)
5. 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:
```typescript
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:
```typescript
// 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
Contributor guide
Assessment
This issue has not been assessed yet.