Skip to content

Make Admin SDK init lazy so builds work without credentials #59

Description

@ameyypawar

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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions