Firebase Google sign-in with Vite and React
In a Vite + React app, read the Firebase config from VITE_-prefixed variables via import.meta.env, keep the signed-in user in a React context fed by onAuthStateChanged, and authorize localhost plus your production domain in Firebase.
To make Google's screen show your domain, proxy /__/auth/* to YOUR-PROJECT.firebaseapp.com on your host (Netlify _redirects or vercel.json) and set VITE_FIREBASE_AUTH_DOMAIN to that domain for production builds only.
Why it goes wrong in Vite apps
- The
VITE_prefix. Vite exposes onlyVITE_-prefixed variables to client code. A variable namedFIREBASE_API_KEYis silentlyundefinedin the browser, andprocess.envisn't how Vite apps read config. - Values are baked in at build time. Vite statically replaces
import.meta.env.VITE_*during the build, so changing a variable on your host does nothing until you rebuild. - Strict Mode runs effects twice in development. React runs an extra setup + cleanup cycle for every effect in development. An auth listener without cleanup leaks a second subscription.
- Unlisted dev and deploy hosts. Vite serves on
localhost:5173(andvite previewon4173). Projects created after April 28, 2025 don't getlocalhostas an authorized domain by default, and your Netlify or Vercel host needs its own entry. - Redirect sign-in.
signInWithRedirect()relies on a cross-origin iframe; Firebase says a workaround is required on Chrome M115+ (from June 24, 2024), Firefox 109+ and Safari 16.1+.
Step-by-step fix
1. Add VITE_ variables, split by mode
# .env (shared, loaded in every mode)
VITE_FIREBASE_API_KEY=your-web-api-key
VITE_FIREBASE_PROJECT_ID=YOUR-PROJECT
VITE_FIREBASE_APP_ID=1:000000000000:web:abc123
# .env.development (used by `vite` / `npm run dev`)
VITE_FIREBASE_AUTH_DOMAIN=YOUR-PROJECT.firebaseapp.com
# .env.production (used by `vite build`)
VITE_FIREBASE_AUTH_DOMAIN=app.example.com
Splitting authDomain by mode lets local development keep Firebase's default handler while production builds use your own domain (set that up in step 5 before you deploy with it). Firebase says API keys restricted to Firebase services don't need to be secret, but don't put real secrets in any VITE_ variable: Vite warns they end up in client code. Keep .env.*.local files out of git.
For TypeScript autocompletion, extend Vite's types:
// src/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_FIREBASE_API_KEY: string;
readonly VITE_FIREBASE_AUTH_DOMAIN: string;
readonly VITE_FIREBASE_PROJECT_ID: string;
readonly VITE_FIREBASE_APP_ID: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
2. Initialize Firebase once
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider } from 'firebase/auth';
const app = initializeApp({
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
});
export const auth = getAuth(app);
export const googleProvider = new GoogleAuthProvider();
A Vite single-page app has no server rendering step, so this module can run at import time.
3. Share auth state with a React context
// src/auth/AuthProvider.tsx
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
import { onAuthStateChanged, signInWithPopup, signOut, type User } from 'firebase/auth';
import { auth, googleProvider } from '../lib/firebase';
type AuthValue = {
user: User | null;
loading: boolean;
signIn: () => Promise<unknown>;
logOut: () => Promise<void>;
};
const AuthContext = createContext<AuthValue | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Returning the unsubscribe function keeps Strict Mode from leaving two listeners
return onAuthStateChanged(auth, (u) => {
setUser(u);
setLoading(false);
});
}, []);
const value: AuthValue = {
user,
loading,
signIn: () => signInWithPopup(auth, googleProvider),
logOut: () => signOut(auth),
};
// React 19 syntax; on React 18 use <AuthContext.Provider value={value}>
return <AuthContext value={value}>{children}</AuthContext>;
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used inside <AuthProvider>');
return ctx;
}
Wrap your app in src/main.tsx:
createRoot(document.getElementById('root')!).render(
<StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</StrictMode>
);
Then use it in any component:
const { user, loading, signIn, logOut } = useAuth();
if (loading) return null;
return user
? <button onClick={logOut}>Sign out {user.displayName}</button>
: <button onClick={signIn}>Sign in with Google</button>;
loading matters because auth.currentUser can be null until Firebase finishes initializing; the observer reports the real state. Turn Google on in the Firebase console under Security > Authentication > Sign-in method (as of Oct 2026; older guides: Build > Authentication).
4. Authorize your hosts
Open Security > Authentication > Settings > Authorized domains (as of Oct 2026; older guides: Build > Authentication > Settings) and use Add domain for:
localhost, fornpm run devandvite preview. Firebase recommends removing it from production projects.- Your production domain, for example
app.example.com. - Your host's default domain (such as
your-site.netlify.app) if you sign in there.
5. Show your own domain on Google's sign-in screen
Firebase's docs say users may see Continue to: https://YOUR-PROJECT.firebaseapp.com during Google sign-in, because the SDK opens https://<authDomain>/__/auth/handler. Firebase's redirect best-practices page (option 3) says to forward /__/auth/ on your domain to YOUR-PROJECT.firebaseapp.com transparently (not with a 302), then set authDomain to your domain. A Vite build is static files, so the forwarding lives on your host.
Netlify. Create public/_redirects. Vite copies public/ to the root of dist, which is where Netlify reads _redirects:
# public/_redirects
/__/auth/* https://YOUR-PROJECT.firebaseapp.com/__/auth/:splat 200
/* /index.html 200
The 200 status makes it a rewrite, so the browser's address stays on your domain while Netlify fetches the Firebase page behind the scenes. Keep the auth line above your SPA fallback, which Netlify suggests listing last.
Test this on a preview deployment before relying on it.
Vercel. Add an external rewrite to vercel.json in the project root:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"rewrites": [
{
"source": "/__/auth/:path*",
"destination": "https://YOUR-PROJECT.firebaseapp.com/__/auth/:path*"
}
]
}
If you also have a catch-all SPA rewrite in that file, list the auth rule first.
Test this on a preview deployment before relying on it.
Firebase Hosting. No proxy needed: Firebase already serves /__/auth/ on your connected custom domain, so you only change authDomain (see the Firebase Hosting guide below).
Then, on Google's side:
- In the Google Cloud console, open APIs & Services > Credentials, choose the web client under OAuth 2.0 Client IDs (as of Oct 2026 also listed under Google Auth Platform > Clients), and add
https://app.example.com/__/auth/handlerto Authorized redirect URIs. Google says this can take 5 minutes to a few hours to apply. - Make sure
app.example.comis in Firebase's authorized domains. - Rebuild and deploy so
.env.productionis baked in.
How to confirm it worked
- Visit
https://app.example.com/__/auth/handler. You should get a Firebase response, not your app'sindex.html. If you see your app, the SPA fallback is matching first. - In DevTools > Network, start sign-in and check that the popup loads
https://app.example.com/__/auth/handler?.... - Google's account chooser should say it will continue to
app.example.com. - Because Vite writes the
import.meta.envvalues into the built files, you can search the deployed JS (DevTools > Sources) forauthDomain. It should showapp.example.com; if it still showsfirebaseapp.com, the production build didn't pick up.env.production.
Related problems
auth/unauthorized-domainonlocalhost:5173: Fix auth/unauthorized-domain on localhost- Same error on a Netlify URL: Fix auth/unauthorized-domain on Netlify
- Redirect sign-in in React returns
null: signInWithRedirect not working in React - Google rejects the localhost handler URI: redirect_uri_mismatch on localhost
- Hosting on Firebase: Firebase Hosting custom domain for Auth
- Should you also proxy
/__/firebase/init.json? Do I need init.json?
FAQ
Why is import.meta.env.VITE_FIREBASE_API_KEY undefined?
Either the name lacks the VITE_ prefix, the .env file isn't in the project root, or you changed it without restarting the dev server or rebuilding. Vite replaces these values statically, so a running build never sees later changes.
Do I need a React context for Firebase auth?
Not strictly, but without one every component sets up its own onAuthStateChanged listener. A single provider subscribes once, exposes user and loading, and cleans up on unmount.
Why does my auth listener fire twice in development?
React Strict Mode runs an extra setup and cleanup cycle for every effect in development only. Return the unsubscribe function from onAuthStateChanged in your effect and the duplicate listener goes away; production builds aren't affected.
Can Vite's server.proxy handle /__/auth in production?
No. server.proxy configures Vite's development server (and vite preview reuses it by default), not your deployed site. In production the /__/auth/* forwarding has to come from your host, such as Netlify _redirects or a vercel.json rewrite.
Sources
- Authenticate Using Google with JavaScript | Firebase Authentication
- Best practices for using signInWithRedirect on browsers that block third-party storage access | Firebase
- Manage Users in Firebase
- Authentication FAQ and troubleshooting | Firebase Authentication
- Learn about and manage API keys for Firebase
- firebase-js-sdk: packages/auth/src/core/util/handler.ts
- Env Variables and Modes | Vite
- Server Options | Vite
- Preview Options | Vite
- Static Asset Handling | Vite
- createContext | React
- StrictMode | React
- Rewrites and proxies | Netlify Docs
- Redirects and rewrites | Netlify Docs
- Rewrites on Vercel
- Manage OAuth Clients | Google Cloud
How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked Oct 27, 2026. Spotted something out of date? Tell us.