I have a Next.js app running on Firebase App Hosting and it was previously working fine.
I recently moved the Next.js app itself from the repo root directory to a subdirectory named "next", which I suspect is what caused the problem, but I can't see what I did wrong in the configuration. I moved package.json
, next.config.ts
, apphosting.yml
, all the TypeScript source files, etc. into the new directory, and added an "apphosting" section to firebase.json
:
"apphosting": [
{
"backendId": "mybackend",
"rootDir": "next",
"ignore": [
"node_modules",
".git",
"firebase-debug.log",
"firebase-debug.*.log",
"functions"
],
"alwaysDeployFromSource": true
}
],
(That section was not present before; it was not created automatically back in April when I initialized the app.)
The only important files that remain in the repo root are .firebaserc
, firebase.json
, firestore.rules
and firebase.indexes.json
(actually I don't think the last two are relevant to this issue).
I can still deploy the app successfully from the CLI with firebase deploy --only apphosting
.
When I merge into the main branch on GitHub, the deploy automatically starts but fails. It looks like somehow the build process isn't finding next/package.json
and the source files. It eventually stops after errors including "No buildpack groups passed detection." Here's the build log, starting where it diverges from the successful CLI deploy:
Status: Downloaded newer image for us-central1-docker.pkg.dev/serverless-runtimes/google-22-full/run/base:public-image-next
===> ANALYZING
Image with name "us-central1-docker.pkg.dev/myfbproject/firebaseapphosting-images/mybackend:build-2025-07-16-004" not found
===> DETECTING
target distro name/version labels not found, reading /etc/os-release file
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.firebasenx@0.0.1 ========
Opting out: nx.json not found
======== Output: google.nodejs.firebasenextjs@0.0.1 ========
(error ID: ecce33ff):
package.json not found
======== Output: google.nodejs.yarn@2.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.firebasenx@0.0.1
err: google.nodejs.firebasenextjs@0.0.1 (1)
fail: google.nodejs.yarn@2.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.firebasenx@0.0.1 ========
Opting out: nx.json not found
======== Output: google.nodejs.firebasenextjs@0.0.1 ========
(error ID: ecce33ff):
package.json not found
======== Output: google.nodejs.pnpm@0.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.firebasenx@0.0.1
err: google.nodejs.firebasenextjs@0.0.1 (1)
fail: google.nodejs.pnpm@0.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.firebasenx@0.0.1 ========
Opting out: nx.json not found
======== Output: google.nodejs.firebasenextjs@0.0.1 ========
(error ID: ecce33ff):
package.json not found
======== Output: google.nodejs.npm@1.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.firebasenx@0.0.1
err: google.nodejs.firebasenextjs@0.0.1 (1)
fail: google.nodejs.npm@1.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.firebasenx@0.0.1 ========
Opting out: nx.json not found
======== Output: google.nodejs.firebaseangular@0.0.1 ========
(error ID: ecce33ff):
package.json not found
======== Output: google.nodejs.yarn@2.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.firebasenx@0.0.1
err: google.nodejs.firebaseangular@0.0.1 (1)
fail: google.nodejs.yarn@2.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.firebasenx@0.0.1 ========
Opting out: nx.json not found
======== Output: google.nodejs.firebaseangular@0.0.1 ========
(error ID: ecce33ff):
package.json not found
======== Output: google.nodejs.pnpm@0.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.firebasenx@0.0.1
err: google.nodejs.firebaseangular@0.0.1 (1)
fail: google.nodejs.pnpm@0.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.firebasenx@0.0.1 ========
Opting out: nx.json not found
======== Output: google.nodejs.firebaseangular@0.0.1 ========
(error ID: ecce33ff):
package.json not found
======== Output: google.nodejs.npm@1.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.firebasenx@0.0.1
err: google.nodejs.firebaseangular@0.0.1 (1)
fail: google.nodejs.npm@1.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.yarn@2.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
fail: google.nodejs.yarn@2.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.pnpm@0.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
fail: google.nodejs.pnpm@0.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.npm@1.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.firebasebundle@0.0.1 ========
Opting in: firebase apphosting application
======== Results ========
fail: google.nodejs.runtime@1.0.0
fail: google.nodejs.npm@1.1.0
pass: google.nodejs.firebasebundle@0.0.1
======== Output: google.config.flex@0.9.1 ========
Opting out: Env var GAE_APPLICATION_YAML_PATH is not set, not a GAE Flex app.
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.yarn@2.1.0 ========
Opting out: package.json not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.config.flex@0.9.1
fail: google.nodejs.runtime@1.0.0
fail: google.nodejs.yarn@2.1.0
pass: google.utils.label-image@0.0.2
======== Output: google.config.flex@0.9.1 ========
Opting out: Env var GAE_APPLICATION_YAML_PATH is not set, not a GAE Flex app.
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.pnpm@0.1.0 ========
Opting out: package.json not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.config.flex@0.9.1
fail: google.nodejs.runtime@1.0.0
fail: google.nodejs.pnpm@0.1.0
pass: google.utils.label-image@0.0.2
======== Output: google.config.flex@0.9.1 ========
Opting out: Env var GAE_APPLICATION_YAML_PATH is not set, not a GAE Flex app.
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.npm@1.1.0 ========
Opting out: package.json not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.config.flex@0.9.1
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.npm@1.1.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.yarn@2.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.appengine@0.9.0 ========
Opting out: X_GOOGLE_TARGET_PLATFORM not set to "gae"
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
fail: google.nodejs.yarn@2.1.0
fail: google.nodejs.appengine@0.9.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.pnpm@0.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.appengine@0.9.0 ========
Opting out: X_GOOGLE_TARGET_PLATFORM not set to "gae"
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
fail: google.nodejs.pnpm@0.1.0
fail: google.nodejs.appengine@0.9.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.nodejs.npm@1.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.appengine@0.9.0 ========
Opting out: X_GOOGLE_TARGET_PLATFORM not set to "gae"
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.nodejs.npm@1.1.0
fail: google.nodejs.appengine@0.9.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.utils.archive-source@0.0.1 ========
Opting out: Env var X_GOOGLE_TARGET_PLATFORM is not set to gcf.
======== Output: google.nodejs.yarn@2.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.legacy-worker@0.1.0 ========
Opting out: Only compatible with nodejs8
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.utils.archive-source@0.0.1
fail: google.nodejs.yarn@2.1.0
fail: google.nodejs.legacy-worker@0.1.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.utils.archive-source@0.0.1 ========
Opting out: Env var X_GOOGLE_TARGET_PLATFORM is not set to gcf.
======== Output: google.nodejs.npm@1.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.legacy-worker@0.1.0 ========
Opting out: Only compatible with nodejs8
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.utils.archive-source@0.0.1
skip: google.nodejs.npm@1.1.0
fail: google.nodejs.legacy-worker@0.1.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.utils.archive-source@0.0.1 ========
Opting out: Env var X_GOOGLE_TARGET_PLATFORM is not set to gcf.
======== Output: google.nodejs.yarn@2.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.functions-framework@0.9.4 ========
Opting out: GOOGLE_FUNCTION_TARGET not set
======== Output: google.config.entrypoint@0.9.0 ========
Opting out: GOOGLE_ENTRYPOINT not set, no valid entrypoint in app.yaml and Procfile not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.utils.archive-source@0.0.1
fail: google.nodejs.yarn@2.1.0
skip: google.nodejs.functions-framework@0.9.4
skip: google.config.entrypoint@0.9.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.utils.archive-source@0.0.1 ========
Opting out: Env var X_GOOGLE_TARGET_PLATFORM is not set to gcf.
======== Output: google.nodejs.pnpm@0.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.functions-framework@0.9.4 ========
Opting out: GOOGLE_FUNCTION_TARGET not set
======== Output: google.config.entrypoint@0.9.0 ========
Opting out: GOOGLE_ENTRYPOINT not set, no valid entrypoint in app.yaml and Procfile not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.utils.archive-source@0.0.1
fail: google.nodejs.pnpm@0.1.0
skip: google.nodejs.functions-framework@0.9.4
skip: google.config.entrypoint@0.9.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.utils.archive-source@0.0.1 ========
Opting out: Env var X_GOOGLE_TARGET_PLATFORM is not set to gcf.
======== Output: google.nodejs.npm@1.1.0 ========
Opting out: package.json not found
======== Output: google.nodejs.functions-framework@0.9.4 ========
Opting out: GOOGLE_FUNCTION_TARGET not set
======== Output: google.config.entrypoint@0.9.0 ========
Opting out: GOOGLE_ENTRYPOINT not set, no valid entrypoint in app.yaml and Procfile not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.utils.archive-source@0.0.1
fail: google.nodejs.npm@1.1.0
skip: google.nodejs.functions-framework@0.9.4
skip: google.config.entrypoint@0.9.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.utils.archive-source@0.0.1 ========
Opting out: Env var X_GOOGLE_TARGET_PLATFORM is not set to gcf.
======== Output: google.nodejs.functions-framework@0.9.4 ========
Opting out: GOOGLE_FUNCTION_TARGET not set
======== Output: google.config.entrypoint@0.9.0 ========
Opting out: GOOGLE_ENTRYPOINT not set, no valid entrypoint in app.yaml and Procfile not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
skip: google.utils.archive-source@0.0.1
fail: google.nodejs.functions-framework@0.9.4
skip: google.config.entrypoint@0.9.0
pass: google.utils.label-image@0.0.2
======== Output: google.nodejs.runtime@1.0.0 ========
Opting out: neither package.json nor any .js files found
======== Output: google.config.entrypoint@0.9.0 ========
Opting out: GOOGLE_ENTRYPOINT not set, no valid entrypoint in app.yaml and Procfile not found
======== Output: google.utils.label-image@0.0.2 ========
Opting in: always enabled
======== Results ========
fail: google.nodejs.runtime@1.0.0
fail: google.config.entrypoint@0.9.0
pass: google.utils.label-image@0.0.2
ERROR: No buildpack groups passed detection.
ERROR: failed to detect: buildpack(s) failed with err
ERROR: failed to build: executing lifecycle: failed with status code: 21
... and that's all I know. I really have no understanding of this build process.
What am I missing?
It turns out the Next.js app root directory is configured in multiple different places. firebase.json
is one; I did that part correctly. I also had to change it in the Firebase console as shown here:
(This shows a test project that I set up to reproduce the problem; for the question as asked, the value of "App root directory" should be "next".)
I don't know why there are different locations for this setting. I guess one applies to CLI deploys and one applies to auto-deploys from GitHub.
Side note: there is a third place to set this for the emulator, in the "emulators" section of firebase.json
.