One of the most complex architectural hurdles for developers migrating to Next.js 14 is managing Firebase Authentication. The symptom is always the same: A user logs in successfully, the dashboard loads, but the moment they hit refresh, they are instantly booted back to the login screen.
This is not a bug in Firebase; it is a fundamental architectural disconnect between Server-Side Rendering (SSR) and Client-Side Storage. As a debugging engineer, I frequently rewrite authentication layers to fix this exact issue. Here is exactly how to bridge the gap.
TL;DR: How to Persist Auth State in Next.js
- 11. Problem: Firebase Client SDK stores tokens in browser IndexedDB. Next.js Server Components cannot read IndexedDB.
- 22. Solution: You must intercept the Firebase ID token on the client and send it to an API route.
- 33. The API route sets a secure, HTTPOnly cookie containing the session.
- 44. Next.js Middleware or Server Components then read this cookie to verify the user *before* rendering the HTML.
Root Causes
The IndexedDB SSR Disconnect
By default, the Firebase Client SDK persists user sessions in the browser's IndexedDB. When a user refreshes a Next.js page, the request hits the Node.js server first. Node.js has absolutely no access to the browser's IndexedDB. Therefore, the server assumes the user is unauthenticated and renders the logged-out UI.
Hydration Flashing
If you don't handle SSR authentication, the server sends a logged-out UI to the browser. A few milliseconds later, the browser JavaScript executes, Firebase checks IndexedDB, finds the user, and abruptly updates the UI to the logged-in state. This causes a massive, unprofessional layout shift (flashing).
Missing HTTPOnly Cookies
The only reliable way to share state between a browser and a Next.js server on the initial page load is through Cookies. If you are not explicitly converting your Firebase ID token into a session cookie, SSR auth will fail.
Step-by-Step Fix Guide
Capture the Firebase ID Token
On the client side, listen for auth state changes. When a user logs in, request their ID token.
import { onIdTokenChanged } from 'firebase/auth';
onIdTokenChanged(auth, async (user) => {
if (user) {
const token = await user.getIdToken();
// Send token to your Next.js API route
await fetch('/api/login', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
} else {
// User logged out, clear cookie
await fetch('/api/logout', { method: 'POST' });
}
});Create the Session Cookie API Route
Create a Next.js API route that takes the ID token, verifies it using the Firebase Admin SDK, and creates a secure session cookie.
import { auth } from 'firebase-admin';
import { cookies } from 'next/headers';
export async function POST(request: Request) {
const token = request.headers.get('Authorization')?.split('Bearer ')[1];
const decodedToken = await auth().verifyIdToken(token);
// Create a 5-day session cookie
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const sessionCookie = await auth().createSessionCookie(token, { expiresIn });
cookies().set('session', sessionCookie, { maxAge: expiresIn, httpOnly: true, secure: true });
return new Response('OK', { status: 200 });
}Protect Routes via Next.js Middleware
Now that the server has the session cookie, use `middleware.ts` to block unauthenticated users from accessing protected routes before the page even renders.
- In `middleware.ts`, read `request.cookies.get('session')`.
- If the cookie is missing and the user is trying to access `/dashboard`, immediately redirect them to `/login`.
- For complex edge cases, consider using libraries like `next-firebase-auth-edge` to handle token refresh and edge runtime compatibility automatically.
Struggling with Next.js SSR Auth?
Connecting Firebase to Next.js App Router is notoriously difficult. If your users are losing their sessions on refresh, let me audit and rewrite your authentication layer today.
Get Urgent Auth SupportRelated Errors
Error: auth/argument-error
Usually occurs in the Firebase Admin SDK when attempting to verify an invalid or expired ID token. Ensure the token is passed correctly without the 'Bearer ' prefix.
Firebase ID token has expired
Tokens expire after 1 hour. Your client-side `onIdTokenChanged` listener should automatically fetch a new token and hit your API route to refresh the session cookie.
Prevention Strategy
- Never rely solely on client-side routing (`useRouter().push('/login')`) for protecting secure dashboards in Next.js. Always use Server Components or Middleware to enforce security boundaries at the network level.
- Ensure your `HttpOnly` cookies are set to `Secure: true` in production to prevent Man-in-the-Middle (MITM) attacks.
Still Stuck With This Issue?
Send your exact error message or deployment issue. I'll respond with a targeted fix.
Need a Deeper Fix?
Describe your full project issue below and I'll get back to you with a targeted fix.