Next.js

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.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

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.

text
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-error

Why 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:

text
### 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, setLogLevel

The 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 build and next dev in Next.js 16, per the Next.js CLI reference) treats the missing export as a warning, compiles the file anyway and binds firebase to undefined. The page then throws at runtime, on the server during prerendering or in the browser.
  • Turbopack (what a plain next build used 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:

text
./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:

text
Export encountered an error on /, exiting the build.
⨯ Next.js build worker exited with code: 1 and signal: null

Fix#

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#

  1. Replace the default import and the side-effect imports with named imports. The getApps guard replaces the old firebase.apps.length check, and services are obtained from the app instead of being attached to a namespace.
js
// 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) };
}
  1. Update the call sites. Code that used firebase.auth() now uses the returned auth with functions imported from firebase/auth, for example a sign-in call:
js
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#

  1. Point the three imports at the compat packages, which ship inside the same firebase package. Nothing else in the file changes, so firebase.apps.length and firebase.initializeApp keep working:
js
import firebase from "firebase/compat/app";
 
import "firebase/compat/auth";
import "firebase/compat/database";
  1. Apply the same change to every other file that imports from firebase/.... One leftover import "firebase/auth" next to firebase/compat/app gives 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:

bash
npm rm firebase
npm install firebase@8.9

On 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:

bash
npx next build

With 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:

text
### AFTER fix 1 (modular imports), Turbopack: next build
✓ Compiled successfully in 6.8s
✓ Generating static pages using 3 workers (3/3) in 246ms

Route 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:

bash
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:

text
Error: Turbopack build failed with 1 error:
./firebaseClient.js:1:1
Error: Export default doesn't exist in target module

Turbopack adds a hint that points at the cause, since the module's exports are fully known:

text
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:

text
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:

text
same options twice -> same instance: true | apps: 1

The 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

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote