I have a Typescript Nx Next.js App running with Firebase (and Firebase Admin). Within this codebase I have a firebase admin util defined as follows -
// File ./utils/FirebaseAdmin.ts
// import checkConfig from './checkConfig';
import firebaseAdmin, { cert } from 'firebase-admin/app';
import 'firebase-admin/auth';
import 'firebase-admin/firestore';
import { getAuth } from 'firebase-admin/auth';
import { getFirestore } from 'firebase-admin/firestore';
let FirebaseAdmin;
const credentials = {
projectId: process.env.NEXT_PUBLIC_FIREADMIN_PROJECT_ID,
privateKey: process.env.NEXT_PUBLIC_FIREADMIN_PRIVATE_KEY,
clientEmail: process.env.NEXT_PUBLIC_FIREADMIN_CLIENT_EMAIL,
};
const options = {
credential: cert(credentials),
};
if (firebaseAdmin.getApps().length === 0) {
// checkConfig(credentials, true);
FirebaseAdmin = firebaseAdmin.initializeApp(options);
}
// export const FirebaseAdmin = getApp();
export const FirebaseAdminAuth = getAuth(FirebaseAdmin);
export const FirestoreAdmin = getFirestore(FirebaseAdmin);
export default FirebaseAdmin;
But when using this either within the page getServerSideProps function or when using this in the nextjs API as follows -
// ./pages/api/projects.ts
import FirebaseAdmin from '../utils/FirebaseAdmin'; // <- at the start of the file
....
....
const SomeMethod = (handler) => {
....
const adminAuth = getAuth(FirebaseAdmin);
....
}
export default SomeMethod;
or when even when referencing FirestoreAdmin from the util I get the below error -
ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
at Module.default (webpack-internal:///./utils/FirebaseAdmin.ts:6:42)
After trying various approaches, and hours of googling around, I am still not close to a solution, this seems like some sort of execution order issue. But I could be wrong. Any clue as to what could be causing this issue or known solution would be very helpful.
Some Package Versions used -
"next": "12.0.0",
"firebase": "^9.5.0",
"firebase-admin": "^10.0.0",
CodePudding user response:
firebase-admin/<package> imports behave like the modular SDK, where they do not have a default export. They also do not mix into a global object like the legacy JavaScript Web SDK.
I recommend only importing the "firebase-admin/app" package and leaving the others on a "as-needed" basis as you can just call getFirestore(app) and getAuth(app) when you need them.
// ./lib/firebase-admin.ts
import { initializeApp } from "firebase-admin/app";
const credentials = { /* ... */ };
export const defaultApp = initializeApp({
credential: cert(credentials)
});
export default defaultApp;
Now I haven't compiled the Admin SDK with Webpack for Next, so if you need to guard against calling initializeApp multiple times (you shouldn't?), use the following instead:
// ./lib/firebase-admin.ts
import { initializeApp, getApp, getApps } from "firebase-admin/app";
export const defaultApp = (() => {
if (getApps().length > 0)
return getApp(); // returns the existing default instance
const credentials = { /* ... */ };
return initializeApp({
credential: cert(credentials)
});
})();
export default defaultApp;
In either case, you would use it as follows:
// usage
import adminApp from "./lib/firebase-admin"; // or import { defaultApp } from "./lib/firebase-admin";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
const firestore = getFirestore(adminApp);
// do something
Addendum
Modular syntax:
// Modular Firebase Web SDK (v9 )
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
const defaultApp = initializeApp(/* ... */);
const defaultAuth = getAuth(defaultApp);
const defaultFirestore = getFirestore(defaultApp);
export {
defaultApp as app, // these are class instances
defaultAuth as auth,
defaultFirestore as firestore // i.e. you can't use firestore.doc(), import it using `import { doc } from "firebase/firestore"` and then call `doc(firestore, ...)`
}
// Modular Firebase Admin Node SDK (v10 )
import { initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
const defaultApp = initializeApp(/* ... */);
const defaultAuth = getAuth(defaultApp);
const defaultFirestore = getFirestore(defaultApp);
export {
defaultApp as app, // these are class instances
defaultAuth as auth,
defaultFirestore as firestore // i.e. you can't use firestore.doc(), import it using `import { doc } from "firebase/firestore"` and then call `doc(firestore, ...)`
}
Legacy syntax:
// Legacy Firebase Web SDK (v8 or older)
import firebase from "firebase/app";
import "firebase/auth"; // mix Auth API into firebase global
import "firebase/firestore"; // mix Firestore API into firebase global
const defaultApp = firebase.initializeApp(/* ... */);
const defaultAuth = firebase.auth(defaultApp);
const defaultFirestore = firebase.firestore(defaultApp);
export {
firebase, // the firebase object & also the namespace
defaultApp as app, // the rest are just class instances - not namespaces!
defaultAuth as auth,
defaultFirestore as firestore // i.e. no firestore.Timestamp, you must use firebase.firestore.Timestamp
}
// Legacy Firebase Admin Node SDK (v10 and older)
import * as admin from "firebase-admin";
const defaultApp = admin.initializeApp(/* ... */);
const defaultAuth = admin.auth(defaultApp);
const defaultFirestore = admin.firestore(defaultApp);
export {
admin, // the admin object & also the namespace
defaultApp as app, // the rest are just class instances - not namespaces!
defaultAuth as auth,
defaultFirestore as firestore // i.e. no firestore.Timestamp, you must use admin.firestore.Timestamp
}
CodePudding user response:
For any one else facing similar issues, what finally seemed to work for me was enabling experimental topLevel await (in next.config.js) -
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nrwl/next/plugins/with-nx');
/**
* @type {import('@nrwl/next/plugins/with-nx').WithNxOptions}
**/
const nextConfig = {
nx: {
// Set this to true if you would like to to use SVGR
// See: https://github.com/gregberge/svgr
svgr: false,
},
webpack: (config) => {
config.experiments = { topLevelAwait: true };
return config;
},
};
module.exports = withNx(nextConfig);
And then wrapped the Firebase initialization in a async-await IIFE (Immediately-invoked Function Expression) -
import {
initializeApp,
getApps,
getApp,
AppOptions,
cert,
ServiceAccount,
} from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import { getFirestore } from 'firebase-admin/firestore';
import checkConfig from './checkFirebaseConfig';
const FirebaseAdmin = await (async () => {
const credentials: ServiceAccount = {
projectId: process.env.NEXT_PUBLIC_FIREADMIN_PROJECT_ID,
privateKey: process.env.NEXT_PUBLIC_FIREADMIN_PRIVATE_KEY.replace(
/\\n/g,
'\n'
),
clientEmail: process.env.NEXT_PUBLIC_FIREADMIN_CLIENT_EMAIL,
};
const options: AppOptions = {
credential: cert(credentials),
};
function createFirebaseAdminApp(config: AppOptions) {
if (getApps().length === 0) {
checkConfig(credentials, true);
return initializeApp(config);
} else {
return getApp();
}
}
const FirebaseAdmin = createFirebaseAdminApp(options);
return FirebaseAdmin;
})();
const FirebaseAdminAuth = getAuth(FirebaseAdmin);
const FirestoreAdmin = getFirestore(FirebaseAdmin);
if (process.env.NODE_ENV === 'test') {
process.env['FIRESTORE_EMULATOR_HOST'] = 'localhost:8080';
FirestoreAdmin.settings({
host: 'localhost:8080',
ssl: false,
});
}
export { FirebaseAdmin, FirebaseAdminAuth, FirestoreAdmin };
Not sure if this is the best approach, but it seems to work for now.
