Skip to content

Repository files navigation

Next.js 16 + Firebase Boilerplate

Next.js + Firebase

Next.js 16 + Firebase boilerplate with server-side authentication and per-user Firestore data, both reached through the Admin SDK. Sign-in exchanges an ID token for an httpOnly session cookie the server verifies; the client never touches Firestore.

Features

Authentication Features

  • ✅ Server Side Authentication
  • ✅ Sign In (Google + Anonymous)
  • ✅ Upgrade Account (Anonymous → Google)
  • ✅ Delete Account

Technical Features

  • ✅ Next.js 16 (App Router)
  • ✅ TypeScript Support
  • ✅ Tailwind CSS Styling
  • ✅ SEO Optimized
  • ✅ Responsive Design
  • ✅ Notification System
  • ✅ Firestore (server-side, per-user)
  • ✅ Vercel Analytics
  • ✅ Tested with Vitest, verified in CI

Demo

Live Demo

Getting Started

Prerequisites

  • Node.js 24 or later. engines asks for >=24.0.0 and CI builds on 24, so Vercel deploys on 24.x.
  • Firebase account with a project created
  • Firebase Admin SDK credentials

Firebase Setup

Step 1: Create a Firebase Project

  1. Go to the Firebase Console
  2. Click "Add project"
  3. Follow the setup instructions

Step 2: Enable Authentication

  1. In your Firebase project console, go to "Authentication"
  2. Click "Get started"
  3. Enable Google and Anonymous sign-in methods

Step 3: Create the Firestore Database

  1. In your Firebase project console, go to "Databases & Storage" > "Firestore"
  2. Click "Create database"
  3. Choose "Standard edition", then "Next"
  4. Keep the Database ID as (default), pick a location, then "Next"
  5. Start in production mode, then "Create"

Production mode denies every read and write from web and mobile clients while still allowing authenticated application servers - which is exactly this boilerplate's shape, and what firestore.rules here already encodes.

This step is required, not optional. A signed-out visitor never touches Firestore, so the page still renders - but the moment anyone signs in, the notes query hits a database that is not there and the whole page fails rather than hiding the misconfiguration. See Per-User Firestore Data for why it fails loudly by design.

Step 4: Generate Admin SDK Credentials

  1. In your Firebase project settings, go to "Service accounts"
  2. Click "Generate new private key"
  3. Save the JSON file and use its contents for the FIREBASE_ADMIN_SERVICE_ACCOUNT environment variable

Step 5: Get Web SDK Configuration

  1. In your Firebase project settings, go to "General"
  2. Under "Your apps", click the web app (create one if needed)
  3. Copy the values into NEXT_PUBLIC_FIREBASE_WEB_SDK_CONFIG, as JSON

The console shows a JavaScript snippet (const firebaseConfig = { apiKey: ... }) with unquoted keys. The variable is read with JSON.parse, so copy only the object, quote every key, and drop the const and any trailing comma - .env.local.example shows the exact shape.

Environment Variables

Copy the example file and fill in your own values:

cp .env.local.example .env.local
Variable Required Purpose
FIREBASE_ADMIN_SERVICE_ACCOUNT yes Service account JSON from Step 4. Verifies sessions, manages users, and is also what reaches Firestore, so it needs Firestore access in the project
NEXT_PUBLIC_FIREBASE_WEB_SDK_CONFIG yes Web app config from Step 5, used by the browser SDK
SITE_URL no Public URL of the deployment, used for metadata, robots.txt and the sitemap. Falls back to the Vercel production domain, then http://localhost:3000

IMPORTANT: both values must be a single physical line of JSON. This is not the same as removing every \n: private_key legitimately contains \n escape sequences inside its string, and they must survive. A real line break in the file is what breaks parsing. jq -c . service-account.json produces the right shape from a downloaded key file.

Installation

  1. Clone the repository
git clone https://github.com/zeikar/nextjs-firebase-boilerplate.git
cd nextjs-firebase-boilerplate
  1. Install dependencies
npm install
# or
yarn install
# or
pnpm install
# or
bun install
  1. Run the development server
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
  1. Open http://localhost:3000 with your browser to see the result.

Other scripts: npm test (run the test suite), npm run test:watch (re-run on change), npm run test:coverage (coverage report), npm run lint (ESLint), npm run build (production build), npm start (serve the build).

Project Structure

app/                       - Next.js App Router
  layout.tsx               - Root layout, notification + auth providers
  page.tsx                 - Homepage
  robots.ts, sitemap.ts    - SEO routes
  globals.css              - Tailwind entry point
  api/auth/
    signin/                - Exchanges an ID token for a session cookie
    signout/               - Revokes the session and clears the cookie
    user/                  - Reads the current user, deletes the account
  api/notes/               - Create and delete the signed-in user's notes
components/
  auth/                    - Sign in/out, upgrade and delete controls
  icons/                   - Icon components
  modals/AuthModal.tsx     - Sign-in modal
  notifications/           - Notification item
  notes/                   - Notes list/form (client) and the server-side reader
contexts/
  auth-context.tsx         - Single auth state for the whole app
  notification-context.tsx - Notification state and container
lib/
  firebase/
    admin.ts               - Firebase Admin SDK setup
    auth-server.ts         - Session verification for server code
    authService.ts         - Client calls to the auth API routes
    client.ts              - Firebase Web SDK setup
    notes.ts               - Per-user notes subcollection and Note type
    session.ts             - Cookie name, lifetime, freshness rule
    useFirebaseAuth.ts     - Auth operations and loading state
  utils/
    firebaseErrors.ts      - Error classification and messages
    request-origin.ts      - Same-origin guard for the state-changing routes
    useFirebaseErrorHandler.ts
  site.ts                  - Public site URL
public/                    - Static files
firestore.rules            - Deny-all Firestore security rules
__tests__/
  helpers/                 - Shared test doubles
  stubs/                   - Module stand-ins for the test resolver
  lib/                     - Pure units: guards, session rules, error mapping
  api/                     - Auth and notes routes, with the Admin SDK mocked
  client/                  - Hooks, contexts and components, under jsdom
.github/workflows/         - Lint, test and build on push and PR

Key Features

Server-Side Authentication

Sign-in exchanges a Firebase ID token for an httpOnly session cookie (2 weeks), which server components and route handlers verify with the Admin SDK. getServerUser() returns the current user for rendering; getServerSession() additionally distinguishes an unusable cookie from a Firebase outage, so a route handler can answer 503 instead of claiming the caller is signed out.

Session Security

The auth routes assume a hostile caller:

  • Same-origin only - the state-changing routes (sign-in, sign-out, delete) reject requests whose Origin does not match the deployment; sign-in and deletion additionally require a JSON content type, so a cross-site form cannot sign a victim into an attacker's account.
  • Fresh tokens only - a session cookie is minted only from an ID token whose sign-in happened in the last 5 minutes, so a leaked ID token cannot be traded for a two-week session. Re-minting is exempt when the browser already holds a valid session for the same user, which is what the anonymous -> Google upgrade does: it grants no access the caller does not already have.
  • Sign-out revokes everywhere - signing out calls revokeRefreshTokens, which invalidates every session of that user on every device. Firebase cannot revoke a single session cookie, so a copied cookie would otherwise stay valid; if revocation fails the API reports it instead of claiming success.
  • Deletion needs re-authentication - deleting an account requires a confirmation and a freshly minted ID token (a Google popup re-auth for permanent accounts), because Admin-side deletion bypasses Firebase's own requires-recent-login rule. It also attempts to remove that user's notes - see Per-User Firestore Data for what happens when that does not finish.

GET /api/auth/user is included as a worked example of a protected route handler; the UI itself reads the user on the server.

Authentication Modal

A ready-to-use authentication modal that supports Google Sign-in and Anonymous authentication, with the ability to upgrade anonymous accounts to permanent ones.

Firebase Error Handling

Built-in error handling for Firebase authentication with user-friendly error messages.

Notification System

A contextual notification system to display success/error messages to users.

Per-User Firestore Data

Notes live at users/{uid}/notes. userNotes(uid) builds a path for whatever uid it is handed, so the layout enforces nothing on its own - ownership rests on every caller passing the uid from the verified session cookie, never one from a request body or a path segment. A server component reads with the Admin SDK; app/api/notes/route.ts writes behind rejectCrossSiteRequest, the same guard the auth routes use. The client never imports firebase/firestore.

Deleting an account removes the Auth user first, then sweeps that user's notes. A failed sweep is reported rather than hidden behind a success, and the user is warned. The server cookie is cleared; the client sign-out is attempted, and a failure there is logged. Notes can still be left behind: a write already past session verification, or a recursiveDelete that fails part-way, leaves documents under a uid nobody can authenticate as again. Nothing here reaps them. A Cloud Functions onDelete trigger is the usual first step but races that same late write, so guaranteed cleanup needs a durable deletion marker and a reaper.

Note text is capped at 200 characters and trimmed. The note count is not capped and the read is unbounded, so a user's page grows with their own notes; a production app would cap or paginate.

A server-component read that fails on one of four transient statuses - unavailable, deadline exceeded, resource exhausted, internal - renders an "unavailable" line in place of the panel, so a blip costs this section and not the rest of the page. Everything else is rethrown, a missing database included, and no error boundary wraps the section, so a misconfiguration takes the page down where you cannot miss it. Route-handler writes and deletes are not classified this way; they report a generic failure. That is deliberate: a setup error hiding behind a permanent soothing message is worse than one that fails loudly.

firestore.rules denies every client read and write. The Admin SDK bypasses rules by design, so they constrain no server code, only the client - and that file becomes the only defense the moment anyone adds client-side Firestore. This repo ships no firebase.json, so the rules are not deployed just by living here: paste the file into the Rules tab of the Firebase console, or run firebase init firestore and then firebase deploy --only firestore:rules.

Deployment

The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.

Set FIREBASE_ADMIN_SERVICE_ACCOUNT and NEXT_PUBLIC_FIREBASE_WEB_SDK_CONFIG in the project's environment variables. SITE_URL is optional on Vercel: the production domain is picked up automatically.

Add every domain the app is served from to Authentication > Settings > Authorized domains in the Firebase console - the deployment's .vercel.app domain, any custom domain, and localhost for local development. This is the domain in the browser's address bar, not the project's authDomain. Google sign-in, account upgrade and deletion of a Google account all open a popup, and an unlisted domain fails every one of them with auth/unauthorized-domain. (Deleting an anonymous account re-authenticates with a fresh ID token instead, so it opens no popup.)

If you forked this repository:

  • Delete public/google*.html - it verifies the original author's Search Console property, not yours.
  • Supply a Terms of Service and a Privacy Policy, or drop the line promising them. components/modals/AuthModal.tsx tells everyone who signs in that they agree to both, and this repository ships neither.
  • Note that social preview images are rendered by https://dogimg.vercel.app, an external service this repo does not control (lib/site.ts). Point SITE_OG_IMAGE at your own asset if you would rather not depend on it.

Check out the Next.js deployment documentation for more details.

Contributing

Contributions are always welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgements


© 2025 Next.js Firebase Boilerplate

About

Next.js 16 + Firebase boilerplate with server-side authentication and per-user Firestore data, both reached through the Admin SDK.

Topics

Resources

Stars

7 stars

Watchers

1 watching

Forks

Contributors

Languages