Supabase

Fix Firestore 'Host has been set in both settings()' error

Why Next.js reloads turn a harmless emulator warning into a hard FirebaseError, what changed in the modular SDK, and a setup that survives every reload.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

Two lines arrive together. The first, Host has been set in both settings() and useEmulator(), emulator host will be used, is only a warning: the Firestore instance already has a non-default host when useEmulator() runs again. The second, Firestore has already been started and its settings can no longer be changed, is the real error: that second call tried to change the settings of an instance that had already served a read. In a Next.js app this happens because your Firebase module runs again in the same JavaScript context, while the Firestore instance it gets back is the old one. Connect the emulator once per instance, with a flag on globalThis. On firebase 11.4.0 and later, passing exactly the same host and port every time is enough, because the SDK now treats an identical second call as a no-op; on older releases the identical call still throws.

The full output from the Stack Overflow question, on firebase 8.1.1:

text
@firebase/firestore: Firestore (8.1.1): Host has been set in both settings() and useEmulator(), emulator host will be used
 
Error [FirebaseError]: Firestore has already been started and its settings can no longer be changed. You can only modify settings before calling any other methods on a Firestore object.

The setup that produced it was the usual one, in a module imported by the pages of a Next.js app:

js
const db = app.firestore();
const auth = firebase.auth();
if (process.env.NODE_ENV === 'development') {
  db.useEmulator('localhost', 8888);
  firebase.auth().useEmulator('http://localhost:9099/');
}

The author notes that the app worked on first start and failed "after some refreshing or navigation among next.js pages", until the dev server was killed.

Two messages, two different conditions#

Reading the SDK source separates the two lines, and a run of the same code in Node 22 confirms it.

In firebase 8.1.1, useEmulator() compares the current host with the default production host. If they differ, it logs the warning. It then calls settings() with the emulator host, and settings() throws if the instance is already running. The check behind the warning only looks at the default host, so a second call with exactly the same arguments still warns: after the first call, the host is localhost:8888, not the default.

Running the question's calls against firebase 8.1.1, with nothing listening on port 8888, shows each condition alone. Calling useEmulator('localhost', 8888) twice, with no read in between, gives the warning and nothing else:

text
[2026-10-07T04:58:32.701Z]  @firebase/firestore: Firestore (8.1.1): Host has been set in both settings() and useEmulator(), emulator host will be used
second useEmulator ok

Add a single get() between the two calls, and the second call fails with the question's error:

text
[2026-10-07T04:58:32.899Z]  @firebase/firestore: Firestore (8.1.1): Host has been set in both settings() and useEmulator(), emulator host will be used
second useEmulator threw: failed-precondition Firestore has already been started and its settings can no longer be changed. You can only modify settings before calling any other methods on a Firestore object.

So one of the answers on the question overstates it when it says that two useEmulator() calls in a row raise the error. They raise the warning. The error needs a read, a write or a listener in between, which is exactly what a running app does between two reloads.

Why a Next.js reload runs the call again#

The Next.js Fast Refresh documentation states it directly: "If you edit a file with exports that aren't React components, Fast Refresh will re-run both that file, and the other files importing it." A firebase.js that exports db is such a file. When it re-runs, app.firestore() (or getFirestore(app)) does not create a new database object: the Firebase app is still registered, so you get back the instance that has been serving reads since the page loaded. Your module then calls useEmulator() on it a second time.

The question's error was printed in the terminal, so it came from the server side of the dev server, not only from the browser. The Fast Refresh page does not describe how the dev server re-evaluates server modules, and this page does not guess: the reproduction below simply runs the module body twice in one Node process, which is the condition that matters. This is consistent with the error appearing "after some refreshing or navigation" and never on a fresh start, without proving the exact trigger inside Next.js. The second answer on the question names hot reloading as the trigger, and the accepted answer suspected that pages with a server-side error made it more likely. Both point at the same thing: the module body runs more than once while the Firestore instance survives.

Code imported by both server and client also runs on the server first. A 'use client' file is still rendered in Node to build the initial HTML, which is why a guard based on window alone breaks during server rendering. The localStorage is not defined after 'use client' page shows that server render in detail.

What the modular SDK changed#

In the current firebase/firestore package, connectFirestoreEmulator() builds the new settings and compares them with the existing ones before doing anything. The source carries the comment: "No-op if the new configuration matches the current configuration. This supports SSR enviornments which might call connectFirestoreEmulator multiple times as a standard practice." That check arrived with PR #8780, "Emulator Idempotency: Firestore". The PR carries no release note, but the comment is present in the source at the release tag for 11.4.0 and absent at the tag for 11.3.1, and the same script confirms the boundary. On firebase 11.3.1, the identical call after a read still throws, without any warning first:

text
second call threw: failed-precondition Firestore has already been started and its settings can no longer be changed. You can only modify settings before calling any other methods on a Firestore object.

On firebase 11.4.0, the same script passes:

text
second call with same host/port: no error

Separately, PR #7339 fixed the false warning by comparing the full host:port string, and renamed the message. The Firestore changelog lists that fix. Current versions therefore print this wording instead:

text
[2026-10-07T04:58:33.929Z]  @firebase/firestore: Firestore (12.19.0): Host has been set in both settings() and connectFirestoreEmulator(), emulator host will be used.

On firebase 12.19.0 (the current release on npm on 7 October 2026), the identical call after a read also passes silently. Changing the host string after a read still fails, exactly as in version 8:

text
second call threw: failed-precondition Firestore has already been started and its settings can no longer be changed. You can only modify settings before calling any other methods on a Firestore object.

Fix#

1. Initialise the app once and reuse the registered instance#

Put the setup in one module, and never call initializeApp() when an app already exists:

ts
// lib/firebase.ts
import { initializeApp, getApps, getApp } from 'firebase/app';
import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';
 
const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
};
 
const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
export const db = getFirestore(app);

2. Connect the emulator with one fixed host and port#

Use the same literal values everywhere, in this one module only. The Firebase emulator guide uses 127.0.0.1 and port 8080 in its web example; whatever you choose, do not mix localhost in one file and 127.0.0.1 in another:

ts
// lib/firebase.ts (continued)
if (process.env.NEXT_PUBLIC_FIREBASE_USE_EMULATOR === 'true') {
  connectFirestoreEmulator(db, '127.0.0.1', 8080);
}

On firebase 11.4.0 or later this is enough: a reload runs the same call with the same arguments, and the SDK ignores it. On an older modular release, upgrade, or wrap the call in the same globalThis flag shown in step 3.

3. Add a process-wide guard when you are on the version 8 API#

The namespaced useEmulator() has no such check, so remember that the call has been made. Store the flag on globalThis, which exists in the browser and in Node, instead of on window:

ts
// lib/firebase.ts, version 8 API
// (on the v9+ compat layer, import from 'firebase/compat/app' and 'firebase/compat/firestore')
import firebase from 'firebase/app';
import 'firebase/firestore';
 
const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
};
 
const app = firebase.apps.length ? firebase.app() : firebase.initializeApp(firebaseConfig);
export const db = app.firestore();
 
const g = globalThis as typeof globalThis & { __firestoreEmulatorConnected?: boolean };
if (process.env.NODE_ENV === 'development' && !g.__firestoreEmulatorConnected) {
  db.useEmulator('localhost', 8888);
  g.__firestoreEmulatorConnected = true;
}

If TypeScript complains about a custom property on window because you kept a window-based flag, the Property does not exist on Window page covers declaration merging. The intersection cast above avoids the global declaration altogether.

The accepted answer warns that a flag set before the emulators are running would stop the app from ever using them. The reproduction does not support that: with nothing listening on port 8888, useEmulator() returned normally and the next read tried the emulator address.

text
[2026-10-07T04:58:33.018Z]  @firebase/firestore: Firestore (8.1.1): Connection GRPC stream error. Code: 14 Message: 14 UNAVAILABLE: No connection established. Last error: Error: connect ECONNREFUSED 127.0.0.1:8888. Resolution note: 

The call stores a setting; it does not open a connection. Start the emulators before you load a page anyway, or the first reads fail as shown.

The Auth emulator in the question's setup follows the same pattern in the current SDK: the connectAuthEmulator source returns without doing anything when Auth has already started and the configuration is identical, and fails when it differs. The same flag can guard both calls.

Verify the fix#

Run the module body twice in one Node process, with a read in between, which is what a reload does. The reproduction did this with step 3's guarded code on firebase 8.1.1: the second pass got back the same Firestore instance and threw nothing.

text
read issued between the two loads: get() on posts/a
v8 second load ok, same instance: true

In the app itself, start the emulators, open a page that reads Firestore, edit and save lib/firebase.ts, then navigate. The warning should not reappear, and the failed-precondition error should be gone. If the console now shows Could not reach Cloud Firestore backend with ECONNREFUSED, the setup is fine and the emulator is simply not listening on the port you passed: check the port in firebase.json.

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 Firebase CLI has already set the host#

There is a modern route to the warning that has nothing to do with reloads. getFirestore() reads a __FIREBASE_DEFAULTS__ object (from globalThis, an environment variable or a cookie, according to the SDK defaults source) and, if it lists a Firestore emulator host, connects to it automatically. The Firebase CLI's web frameworks integration writes the running emulators into that object (firebase-tools source). If your code then calls connectFirestoreEmulator() with a different spelling of the same host, the instance already points somewhere else. Reproduced with the defaults set to 127.0.0.1:8080 and a manual call to localhost:

text
host after getFirestore: 127.0.0.1:8080
[2026-10-07T04:58:34.963Z]  @firebase/firestore: Firestore (12.19.0): Host has been set in both settings() and connectFirestoreEmulator(), emulator host will be used.
host after connectFirestoreEmulator: localhost:8080

Before the first read this only warns, and your call wins. Run it after a read and it is the "already been started" error again. Either drop the manual call when you run under the CLI, or pass the exact host the CLI uses.

How this was checked#

The reproduction ran on 7 October 2026 in the official node:22-bookworm-slim Docker image (Node 22.23.3), with firebase 8.1.1, 11.3.1, 11.4.0 and 12.19.0 installed side by side and no emulator running, so every connection attempt was refused by design. The Fast Refresh behaviour comes from the Next.js documentation, not from a Next.js run. If, once the emulator is connected, your security rules reject the reads, the Firestore PERMISSION_DENIED guide shows how to replay them against the emulator, and if collection() complains about its first argument after the refactor, the Expected first argument to collection() page covers the missing db instance.

Further reading: Fix 'Failed to get document because the client is offline'.

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