firebase / firebase/apphosting-adapters

copyResources skips merging public/ directory in monorepo standalone builds

Open
#499 2 comments 3 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.