Firebase Google sign-in with Vite and React

Updated Oct 27, 2026 · Pewterkit Guides

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 only VITE_-prefixed variables to client code. A variable named FIREBASE_API_KEY is silently undefined in the browser, and process.env isn'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 (and vite preview on 4173). Projects created after April 28, 2025 don't get localhost as 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, for npm run dev and vite 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:

  1. 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/handler to Authorized redirect URIs. Google says this can take 5 minutes to a few hours to apply.
  2. Make sure app.example.com is in Firebase's authorized domains.
  3. Rebuild and deploy so .env.production is baked in.

How to confirm it worked

  1. Visit https://app.example.com/__/auth/handler. You should get a Firebase response, not your app's index.html. If you see your app, the SPA fallback is matching first.
  2. In DevTools > Network, start sign-in and check that the popup loads https://app.example.com/__/auth/handler?....
  3. Google's account chooser should say it will continue to app.example.com.
  4. Because Vite writes the import.meta.env values into the built files, you can search the deployed JS (DevTools > Sources) for authDomain. It should show app.example.com; if it still shows firebaseapp.com, the production build didn't pick up .env.production.

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

  1. Authenticate Using Google with JavaScript | Firebase Authentication
  2. Best practices for using signInWithRedirect on browsers that block third-party storage access | Firebase
  3. Manage Users in Firebase
  4. Authentication FAQ and troubleshooting | Firebase Authentication
  5. Learn about and manage API keys for Firebase
  6. firebase-js-sdk: packages/auth/src/core/util/handler.ts
  7. Env Variables and Modes | Vite
  8. Server Options | Vite
  9. Preview Options | Vite
  10. Static Asset Handling | Vite
  11. createContext | React
  12. StrictMode | React
  13. Rewrites and proxies | Netlify Docs
  14. Redirects and rewrites | Netlify Docs
  15. Rewrites on Vercel
  16. 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.

Related guides