Fix Cannot read properties of undefined (reading 'apps')
A Firebase v8 tutorial pasted into a project with a current firebase package: why firebase.apps is undefined, the two imports that fix it, and what Turbopack prints instead.
TL;DR#
TypeError: Cannot read properties of undefined (reading 'apps') appears when code written for the Firebase v8 SDK runs against the v9-or-later firebase package. The v8 pattern import firebase from "firebase/app" relies on a default export that the modular SDK no longer has, so firebase is undefined and the guard firebase.apps.length is the first line to crash. Either switch the file to the modular API (named imports of initializeApp, getApps and getApp), or keep the v8 code and point every import at firebase/compat/....
This is the case in the Stack Overflow question (9,500+ views), where an auth tutorial from 2020 was followed with a freshly installed firebase package. The accepted answer names the cause: the code is v8, the installed package is v9.
TypeError: Cannot read properties of undefined (reading 'apps')
at i (.next/server/pages/index.js:1:1757)
at h (.next/server/pages/index.js:1:2194)
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-errorWhy it happens#
Firebase shipped two API styles. The v8 SDK exposed one firebase namespace object: you imported it as a default export, side-effect imports such as firebase/auth attached services to it, and everything hung off dots (firebase.apps, firebase.initializeApp, firebase.auth()). From v9 onwards, firebase/app is a modular entry point made of named functions, designed so a bundler can drop what you do not use. Firebase's upgrade guide from the namespaced API to the modular API shows the same v8 import pattern as the question under the heading "Before: version 8 or earlier".
The installed module confirms there is no default to import. Listing the exports of firebase/app from firebase 13.0.0 (the current release on 10 October 2026) gives only named bindings:
### What firebase/app actually exports: node exports.mjs
false FirebaseError, SDK_VERSION, _DEFAULT_ENTRY_NAME, _addComponent, _addOrOverwriteComponent, _apps, _clearComponents, _components, _getProvider, _isFirebaseApp, _isFirebaseServerApp, _isFirebaseServerAppSettings, _registerComponent, _removeServiceInstance, _serverApps, deleteApp, getApp, getApps, initializeApp, initializeServerApp, onLog, registerVersion, setLogLevelThe false is the answer to "is there a default export?". What happens next depends on who loads the file:
- webpack (selected with the webpack flag of
next buildandnext devin Next.js 16, per the Next.js CLI reference) treats the missing export as a warning, compiles the file anyway and bindsfirebasetoundefined. The page then throws at runtime, on the server during prerendering or in the browser. - Turbopack (what a plain
next buildused on Next.js 16.4.0 in the reproduction) refuses to compile it. - Plain Node.js refuses to link the module at all.
All three were reproduced on 10 October 2026 in a node:22-bookworm container with Next.js 16.4.0, firebase 13.0.0 and the question's firebaseClient.js (demo config values), called from a Pages Router page. With webpack the build prints the warning first, which is the clue that is easy to scroll past:
./firebaseClient.js
Attempted import error: 'firebase/app' does not contain a default export (imported as 'firebase').Then prerendering the page runs firebaseClient(), throws the TypeError shown at the top of this page, and the build stops:
Export encountered an error on /, exiting the build.
⨯ Next.js build worker exited with code: 1 and signal: nullFix#
Pick one route. Route 1 is where Firebase wants you to end up; route 2 is the smallest change when a tutorial or an older codebase is full of namespaced calls.
Route 1: rewrite the initialisation with modular imports#
- Replace the default import and the side-effect imports with named imports. The getApps guard replaces the old
firebase.apps.lengthcheck, and services are obtained from the app instead of being attached to a namespace.
// firebaseClient.js
import { initializeApp, getApps, getApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getDatabase } from "firebase/database";
const FIREBASE_CONFIG = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
export default function firebaseClient() {
const app = getApps().length ? getApp() : initializeApp(FIREBASE_CONFIG);
return { app, auth: getAuth(app), db: getDatabase(app) };
}- Update the call sites. Code that used
firebase.auth()now uses the returnedauthwith functions imported fromfirebase/auth, for example a sign-in call:
import { signInWithEmailAndPassword } from "firebase/auth";
import firebaseClient from "../firebaseClient";
export async function login(email, password) {
const { auth } = firebaseClient();
return signInWithEmailAndPassword(auth, email, password);
}The prefix matters because this file runs in the browser: the Next.js environment variables guide explains that only variables prefixed with NEXT_PUBLIC_ are inlined into the JavaScript sent to the client, with their value frozen at build time. Without the prefix, every field of the config object is undefined in the browser.
Route 2: keep the v8 code, change only the import paths#
- Point the three imports at the compat packages, which ship inside the same
firebasepackage. Nothing else in the file changes, sofirebase.apps.lengthandfirebase.initializeAppkeep working:
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/database";- Apply the same change to every other file that imports from
firebase/.... One leftoverimport "firebase/auth"next tofirebase/compat/appgives you two different API styles in the same bundle.
Firebase's upgrade guide describes compat as the first step of a migration, not a destination: it calls the compat libraries "a temporary solution that will be removed completely in a future major SDK version". They are still present in firebase 13.0.0 (both builds below passed on it), but plan the move to route 1.
The accepted answer's route: downgrading#
The accepted answer on Stack Overflow uninstalls the package and installs the 8.x line:
npm rm firebase
npm install firebase@8.9On the npm registry that range matches 8.9.0 and 8.9.1, and npm installs the highest, 8.9.1. The modular firebase 9.0.0 was published on 25 August 2021, and the last 8.x release on the registry is 8.10.1, from 28 January 2022. The downgrade lets the tutorial code keep its v8 imports, but it pins the project to that line, while Firebase's current documentation, including the upgrade guide linked above, is written for the modular API. Prefer route 1 or route 2.
Verify the fix#
Run a production build, because it prerenders the page and executes the initialisation on the server, which is where the question's terminal error came from:
npx next buildWith route 1 on Next.js 16.4.0 and firebase 13.0.0, both bundlers compiled and prerendered the page. The reproduction used literal demo values in the config object; with the environment variables shown above, they must be set (for example in .env.local) before next build runs, because Next.js inlines them at build time:
### AFTER fix 1 (modular imports), Turbopack: next build
✓ Compiled successfully in 6.8s
✓ Generating static pages using 3 workers (3/3) in 246msRoute 2, with only the three import paths changed, also passed under webpack and Turbopack. Then check the build: under Turbopack (the default for next build and next dev) it must pass with no Export default doesn't exist in target module error, and under next build --webpack the Attempted import error warning must be gone. Then no file still imports the v8 default.
To find stragglers in a larger project, search for the old paths:
grep -rnE "from ['\"]firebase/app['\"]|import ['\"]firebase/(auth|database|firestore)['\"]" --include=*.js --include=*.jsx --include=*.ts --include=*.tsx --exclude-dir=node_modules --exclude-dir=.next .On route 1 the default import is the only problem line; the named imports from firebase/app are correct. On route 2 every match is a file still to convert.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
When the same mistake shows a different message#
On Next.js 16.4.0, a plain next build ran with Turbopack in the reproduction, and the reader of an old tutorial may never see the apps error. The same file stops the build at compile time:
Error: Turbopack build failed with 1 error:
./firebaseClient.js:1:1
Error: Export default doesn't exist in target moduleTurbopack adds a hint that points at the cause, since the module's exports are fully known:
All exports of the module are statically known (It doesn't have dynamic exports). So it's known statically that the requested export doesn't exist.Running the same import in a plain Node.js ES module (a seed script, a test runner without a bundler) fails earlier still, at link time:
SyntaxError: The requested module 'firebase/app' does not provide an export named 'default'Three messages, one cause, one fix: the default import of firebase/app. For a different webpack build failure, caused by the exports map of one firebase 9.x release, see Firebase's "Default condition should be last one" build error.
Why keep the getApps guard#
The guard in route 1 is not decoration. In the reproduction on firebase 13.0.0, calling initializeApp twice with identical options returned the same app instance and left one app registered:
same options twice -> same instance: true | apps: 1The second call, with a different project ID, failed with the code app/duplicate-app and the message "Firebase App named '[DEFAULT]' already exists with different options".
In a Next.js app the module that initialises Firebase can run more than once in the same JavaScript context, and with the guard a second run reads the existing app instead of initialising again, whatever options it was given. The same "module ran twice" behaviour is behind the Firestore "Host has been set in both settings()" error, where the emulator connection is the call that cannot be repeated.
Once the app initialises, a next error to expect when rewriting Firestore calls for the modular API is FirebaseError: Expected first argument to collection(), which means the database instance was not passed in; it is covered in the collection() fix. If the migration has you weighing whether to stay on Firebase at all, the Supabase vs Firebase comparison sets the two side by side. For this error, though, the change is one line per import: nothing in your Firebase project, rules or config needs to move.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
FirebaseError: Expected first argument to collection() — Fix
When Firestore throws 'Expected first argument to collection()', it's almost always a missing Firestore instance. Learn the exact fix for Next.js and v9 modular SDK.
Firebase Auth IDBDatabase Transaction Error: 2026 Fix
Firebase Auth stores its session in an IndexedDB database called firebaseLocalStorageDb. When that connection closes mid-session, sign-in throws InvalidStateError. Here is the cause, the SDK version that changed the behaviour, and a Next.js App Router setup that degrades gracefully.
Fix "Default condition should be last one" (Firebase)
One Firebase release put "default" before "browser" in the package.json exports map of 30 packages, and webpack's resolver refuses that. Here is the exact error, the diff that caused it, the release that fixed it, and how to find any other package in node_modules doing the same thing.


