Skip to content
Next.js+Firebase
Stack Integration

Next.js + Firebase Integration Guide

Firebase Auth and Firestore integrate cleanly with Next.js App Router, but server components require the Firebase Admin SDK — not the client SDK.

Use Cases
  1. Firebase Auth with session cookies for server-side route protection
  2. Firestore reads in Next.js Server Components via Admin SDK
  3. Client-side real-time Firestore listeners in client components
  4. Cloud Functions triggered by Firestore writes, called from Next.js API routes
Implementation

Two Firebase SDKs in play: firebase (client-side, for Auth UI and real-time listeners) and firebase-admin (server-side, for Server Components and Route Handlers). Initialize admin once in a singleton module. Session management: use Firebase Auth + a session cookie verified server-side via admin.auth().verifySessionCookie(). Never put service account credentials in client components or expose them to the browser — admin SDK is server-only.

In detail

How the pieces connect

The split runs along the server/client boundary. In the browser, the firebase client SDK handles the interactive parts: signInWithEmailAndPassword, onAuthStateChanged, and real-time onSnapshot listeners that keep client components live. On the server, the firebase-admin SDK runs inside Server Components and Route Handlers with full privileges via a service account.

The bridge between them is the session cookie. After a client login, you send the Firebase ID token to a Route Handler, which calls admin.auth().createSessionCookie(idToken, { expiresIn }) and sets it as an httpOnly cookie. On every server render, admin.auth().verifySessionCookie(cookie, true) decodes that cookie and tells you who the user is — no client SDK on the server, no exposed credentials. Server Components read Firestore through the Admin SDK; client components subscribe through the client SDK.

Production gotchas

  • Admin SDK re-initialization. Next.js hot-reload and serverless invocations re-run module code, and calling initializeApp() twice throws. Guard it: getApps().length ? getApp() : initializeApp({ credential: cert(...) }), exported from one singleton module.
  • Service account in env, not in the bundle. Keep credentials in server-only env vars (no NEXT_PUBLIC_ prefix). Anything Admin-related imported into a client component will either fail the build or leak secrets.
  • Session cookie revocation. Pass true as the second arg to verifySessionCookie to check revocation — without it, a signed-out or disabled user keeps a valid session until expiry.
  • Stale client tokens. ID tokens expire after one hour; the client SDK refreshes them automatically, but if you mint session cookies from a token you cached too long, createSessionCookie rejects it. Always read a fresh token with getIdToken() right before the exchange.

Firestore reads: where each runs

Decide per route whether data is static, request-time, or live. Server Component reads via the Admin SDK bypass Firestore security rules entirely — the service account is trusted, so authorization is your code's job, not the rules engine's. Check the verified user against the document before returning it; never assume the rules will catch it.

Client-side onSnapshot listeners are the opposite: they run as the signed-in user and are governed by your firestore.rules. Use them for data that must update live without a refetch — chat, presence, dashboards. Mixing the two is normal: render the first paint from a Server Component (fast, indexable) and attach a client listener on mount to take over real-time updates. Watch read costs — each onSnapshot re-delivers every changed document, and a broad query left mounted bills per document on every write.

When this combo fits — and when it doesn't

It fits when you want server-rendered, indexable pages plus genuine real-time data, and you're comfortable owning two SDKs and the session-cookie bridge. Firebase Auth's provider support and Firestore's onSnapshot are hard to match if live updates matter.

It fits less well when your data is heavily relational — Firestore has no joins, so you denormalize and fan-out writes, which gets awkward fast. The Admin SDK also pulls a Node runtime, so Server Components and Route Handlers that touch it can't run on the Edge runtime; keep them on the Node.js runtime. If you only need auth and a SQL-shaped schema, a Postgres-backed option is usually simpler. Choose this stack for real-time-first products where the read patterns are document-shaped, not for report-heavy apps full of cross-entity queries.

Other integration guidesView all →
Related

Need this built?