Modern Fullstack Guide: Next.js & Firebase Auth
This guide covers building modern full-stack applications using Next.js (Frontend/API Routes) and Firebase (Authentication/Firestore).
1. Firebase Setup
Initialization (utils/firebase.js)
Initialize the Firebase SDK using environment variables.
import firebase from "firebase/app";
import "firebase/auth";
const firebaseCredentials = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
// Prevent multiple initializations during hot-reloads
if (!firebase.apps.length) {
firebase.initializeApp(firebaseCredentials);
}
export default firebase;
2. The Authentication Hook
Create a custom React hook to manage the Firebase auth state and provide utility functions.
utils/useFirebaseAuth.jsx
import { useState, useEffect } from "react";
import firebase from "./firebase";
export default function useFirebaseAuth() {
const [authUser, setAuthUser] = useState(null);
const [loading, setLoading] = useState(true);
// Observer for auth state changes
const authStateChanged = async (authState) => {
if (!authState) {
setAuthUser(null);
setLoading(false);
return;
}
setAuthUser({
uid: authState.uid,
email: authState.email,
});
setLoading(false);
};
const signInWithEmailAndPassword = (email, password) =>
firebase.auth().signInWithEmailAndPassword(email, password);
const createUserWithEmailAndPassword = (email, password) =>
firebase.auth().createUserWithEmailAndPassword(email, password);
const signOut = () => firebase.auth().signOut();
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(authStateChanged);
return () => unsubscribe();
}, []);
return {
authUser,
loading,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signOut,
};
}
3. Auth Context API
To make the user state accessible throughout the entire app without “prop drilling,” use the React Context API.
contexts/AuthUserContext.jsx
import { createContext, useContext } from "react";
import useFirebaseAuth from "../utils/useFirebaseAuth";
const authUserContext = createContext({
authUser: null,
loading: true,
signInWithEmailAndPassword: async () => {},
createUserWithEmailAndPassword: async () => {},
signOut: async () => {},
});
export function AuthUserProvider({ children }) {
const auth = useFirebaseAuth();
return (
<authUserContext.Provider value={auth}>
{children}
</authUserContext.Provider>
);
}
// Custom hook for consuming the context
export const useAuth = () => useContext(authUserContext);
Wrapping the App (pages/_app.js)
import { AuthUserProvider } from "../contexts/AuthUserContext";
function MyApp({ Component, pageProps }) {
return (
<AuthUserProvider>
<Component {...pageProps} />
</AuthUserProvider>
);
}
export default MyApp;
4. Protected Routes
Use the useAuth hook to redirect unauthorized users.
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useAuth } from "../contexts/AuthUserContext";
const Dashboard = () => {
const { authUser, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !authUser) {
router.push("/login");
}
}, [authUser, loading]);
if (loading) return <div>Loading...</div>;
return <h1>Welcome back, {authUser.email}</h1>;
};
See Also
- React Basics Guide - Understanding Hooks and Context.
- CI/CD Pipeline Guide - Deploying Next.js to platforms like Vercel.