src/lib/firebase-admin.ts initialises the Admin SDK at module scope, so merely importing it requires a working service-account credential. That makes npm run build fail for anyone without production credentials — which is every contributor, and any CI job.
Location
src/lib/firebase-admin.ts — the eager exports:
export const adminDb: Firestore = getFirestore(getAdminApp());
export const adminAuth: Auth = getAuth(getAdminApp());
getAdminApp() calls loadAdminCredential(), which throws when neither FIREBASE_SERVICE_ACCOUNT_JSON nor FIREBASE_SERVICE_ACCOUNT_PATH is set.
- Imported by all six
'use server' service modules and by src/app/api/auth/session/route.ts.
Reproduction
In a clone with no .env.local and no credential environment variables:
$ npm run build
Collecting page data ...
Error: Firebase Admin credentials are not configured. Set FIREBASE_SERVICE_ACCOUNT_JSON ...
[Error: Failed to collect page data for /api/auth/session]
next build loads route modules during "Collecting page data", which triggers the module-scope initialisation before any request exists.
Note this is invisible locally once .env.local is populated, which is why it went unnoticed.
Why this matters
Three distinct consequences:
- Contributors cannot build the project. Production credentials for the
v-threads Firebase project cannot be handed to outside contributors, so as things stand a newcomer cannot get npm run build to pass at all. npm run dev appears to work because dev compiles lazily — the failure only surfaces when a server action or /api/auth/session is actually hit, which makes it harder to diagnose, not easier.
- CI needs a workaround to compile at all. The only way to make a credential-free build succeed today is to mint a throwaway RSA service-account key at build time, because
cert() cryptographically parses private_key and rejects a placeholder string (error:1E08010C:DECODER routines::unsupported). That is a real workaround that works, but it is load-bearing scaffolding for a problem that should not exist.
- The failure mode is misleading. A missing credential surfaces as "Failed to collect page data", pointing at the route rather than at the credential loader.
Suggested fix
Make initialisation lazy so importing the module is free and the credential is only required when Firestore or Auth is actually used:
let dbInstance: Firestore | undefined;
export function getAdminDb(): Firestore {
return (dbInstance ??= getFirestore(getAdminApp()));
}
let authInstance: Auth | undefined;
export function getAdminAuth(): Auth {
return (authInstance ??= getAuth(getAdminApp()));
}
Then update the call sites — the six service modules in src/lib/services/, src/lib/auth/session.ts, and src/app/api/auth/session/route.ts — to call the accessor instead of importing the constant. The change is mechanical but touches every service module, so it is worth doing as its own commit rather than folded into unrelated work.
Keep loadAdminCredential() and its error message exactly as they are; the goal is only to defer when it runs.
Once this lands, CI needs no dummy credential and a contributor can build and run the app against the Firebase emulators with no production access.
src/lib/firebase-admin.tsinitialises the Admin SDK at module scope, so merely importing it requires a working service-account credential. That makesnpm run buildfail for anyone without production credentials — which is every contributor, and any CI job.Location
src/lib/firebase-admin.ts— the eager exports:getAdminApp()callsloadAdminCredential(), which throws when neitherFIREBASE_SERVICE_ACCOUNT_JSONnorFIREBASE_SERVICE_ACCOUNT_PATHis set.'use server'service modules and bysrc/app/api/auth/session/route.ts.Reproduction
In a clone with no
.env.localand no credential environment variables:next buildloads route modules during "Collecting page data", which triggers the module-scope initialisation before any request exists.Note this is invisible locally once
.env.localis populated, which is why it went unnoticed.Why this matters
Three distinct consequences:
v-threadsFirebase project cannot be handed to outside contributors, so as things stand a newcomer cannot getnpm run buildto pass at all.npm run devappears to work because dev compiles lazily — the failure only surfaces when a server action or/api/auth/sessionis actually hit, which makes it harder to diagnose, not easier.cert()cryptographically parsesprivate_keyand rejects a placeholder string (error:1E08010C:DECODER routines::unsupported). That is a real workaround that works, but it is load-bearing scaffolding for a problem that should not exist.Suggested fix
Make initialisation lazy so importing the module is free and the credential is only required when Firestore or Auth is actually used:
Then update the call sites — the six service modules in
src/lib/services/,src/lib/auth/session.ts, andsrc/app/api/auth/session/route.ts— to call the accessor instead of importing the constant. The change is mechanical but touches every service module, so it is worth doing as its own commit rather than folded into unrelated work.Keep
loadAdminCredential()and its error message exactly as they are; the goal is only to defer when it runs.Once this lands, CI needs no dummy credential and a contributor can build and run the app against the Firebase emulators with no production access.