React SPA with Firebase
A project recipe for a React / TypeScript single-page application using Firebase as a Backend-as-a-Service. Firestore handles data persistence via the client SDK, Firebase Auth manages identity, Firebase Hosting serves the SPA, and Cloud Run handles backend business logic triggered by Firestore events through Eventarc. Use this as a blueprint when estimating, scoping, and delivering a typical B2B internal tool or admin panel — the serverless BaaS alternative to the React SPA on Cloud Run recipe.
When to Use This Recipe
This recipe and the React SPA on Cloud Run recipe solve the same class of problem — a CRUD-heavy internal tool. Choose based on your project's constraints.
| Concern | This recipe (Firebase BaaS) | REST API recipe |
|---|---|---|
| Backend team | Minimal or none — frontend talks to Firestore directly | Dedicated backend team building REST endpoints |
| Real-time data | Built-in via Firestore onSnapshot | Requires WebSockets or polling on top of REST |
| Business logic | Cloud Run triggered asynchronously via Eventarc | Synchronous REST API endpoints |
| Data model complexity | Simple documents and collections, limited joins | Arbitrary SQL/NoSQL with complex queries and joins |
| Multi-client API surface | No formal API contract — clients use SDK directly | Shared REST contract consumed by web, mobile, third parties |
| Offline support | Built-in (Firestore IndexedDB cache) | Manual implementation (service workers, etc.) |
| Cost predictability | Per-operation pricing — can spike with real-time listeners | Compute-based pricing — more predictable at scale |
| Enterprise readiness | See caution below | Published SLAs for Cloud Run, standard GCP support |
Evaluate these factors carefully for enterprise engagements:
- SLAs — Firebase-specific services (Auth, Hosting) do not publish individual SLAs in the way core GCP products do.
- Data residency — Firebase Auth data residency cannot be specified — user identity data is Google-managed, which is a concern for GDPR and data-residency requirements.
- Vendor lock-in — Firestore Security Rules are a proprietary DSL with no migration path to other providers.
- Cost predictability — Per-operation Firestore pricing can become unpredictable under heavy real-time listener workloads.
For enterprise clients, consider upgrading to Identity Platform for SLA guarantees and advanced features like multi-tenancy and SAML SSO. See Requirement Engineering for the full project requirements checklist.
- Choose Firebase BaaS when: rapid prototyping, real-time features are core, small-to-medium data volume, limited backend team, simple CRUD, time-to-market is critical.
- Choose REST API when: complex business logic, complex queries or joins, enterprise data contracts and SLAs are required, multi-client API surface, cost predictability is critical, regulatory data-residency requirements.
Project Overview
This recipe targets the same engagement profile as the REST API recipe: a client-side rendered CRUD application behind authentication — user management dashboards, back-office tools, or internal admin panels. The difference is how data flows. The client talks to Firestore directly via the SDK; business logic runs asynchronously on Cloud Run triggered by Firestore events through Eventarc.
| Decision | Choice |
|---|---|
| Rendering | Client-side SPA |
| Hosting | Firebase Hosting |
| Language | TypeScript |
| UI framework | React |
| Backend integration | Firestore SDK (BaaS) |
| Backend business logic | Cloud Run via Eventarc |
| Auth | Firebase Authentication |
| Primary state model | Firestore real-time listeners + Zustand for client state |
Tech Stack
Core
| Library | Role |
|---|---|
| React | UI library |
| TypeScript | Type-safe JavaScript |
| Vite | Build tool and dev server |
Styling & UI
| Library | Role |
|---|---|
| Tailwind CSS | Utility-first CSS |
| shadcn/ui | Component collection (Radix + Tailwind) |
State & Data
| Library | Role |
|---|---|
Firebase JS SDK (firebase/firestore, firebase/auth) | Firestore data access, auth, real-time listeners |
| Zustand | Client-only state management |
In the REST API recipe, TanStack Query manages server state — caching, background refetching, stale-while-revalidate, and optimistic updates. The Firestore SDK provides all of these natively: real-time listeners (onSnapshot) push updates automatically, IndexedDB caching enables offline reads, and writes are applied to the local cache before server confirmation. Combining TanStack Query with Firestore creates two competing cache layers and removes the benefit of real-time sync. Use Firestore's built-in capabilities instead.
Routing & Forms
| Library | Role |
|---|---|
| React Router | Client-side routing |
| React Hook Form | Form state management |
| Zod | Schema validation |
| date-fns | Date utilities |
| Sonner | Toast notifications |
Build & Testing
| Library | Role |
|---|---|
| Vitest | Unit and integration testing |
| Playwright | End-to-end testing |
All core choices align with the Recommended Tech Stack. See individual pages for rationale and alternatives.
Architecture Overview
The application follows a BaaS architecture. The browser loads the React SPA from Firebase Hosting's global CDN. The SPA reads and writes data directly to Firestore via the client SDK, protected by Security Rules. Firebase Auth handles user identity. When a document changes in Firestore, Eventarc routes the event to a Cloud Run service that executes backend business logic and may write results back to Firestore. The client sees these results automatically via its real-time listener — no polling, no second API call.
The user's browser downloads the SPA bundle from Firebase Hosting's CDN. The SPA communicates directly with Firestore for all data operations and with Firebase Auth for identity. Firebase-issued auth tokens are evaluated automatically by Firestore Security Rules on every operation. When documents change, Eventarc delivers the event to a Cloud Run service that runs backend business logic. The Cloud Run service writes results back to Firestore, and the client's real-time listener picks up the update automatically.
SPA Component Architecture
Same structural pattern as the REST API recipe — app shell, router, feature modules. The difference is that feature modules use custom hooks wrapping Firestore SDK calls (onSnapshot for real-time data, getDocs/getDoc for one-off reads) instead of TanStack Query hooks wrapping a REST client.
Eventarc Flow
The client writes a document and immediately sees the "pending" state via its real-time listener. Firestore emits a document event routed by Eventarc to the Cloud Run service. After the service completes its business logic and writes the result back to Firestore, the client automatically receives the "confirmed" state — no polling or manual refetch required.
Key Patterns
Firestore Data Layer. Custom hooks per feature module that wrap Firestore SDK calls. For real-time data, use onSnapshot inside a useEffect with proper cleanup (unsubscribe on unmount). For one-time reads (reports, exports), use getDocs. Define a consistent collection path structure and use TypeScript converter objects (FirestoreDataConverter) to enforce type safety between Firestore documents and application types. Keep reads and writes co-located in per-feature service files.
In the REST API recipe, the backend API validates and authorizes every request. In this recipe, Firestore Security Rules serve that role. Rules are evaluated server-side on every read and write — they cannot be bypassed from the client. Treat Security Rules as production code: version-control your firestore.rules file, write unit tests with the Firebase Emulator Suite, and review rule changes in PRs. Weak or overly permissive rules are the most common security risk in Firebase BaaS applications.
Firebase Security Rules. Structure rules to validate authentication (request.auth != null), ownership or role (request.auth.uid == resource.data.ownerId), and data shape on writes. Use custom claims for role-based access control rather than storing roles in user-editable documents. Keep rules simple — complex rules with many cross-document reads add latency and cost.
Eventarc & Cloud Run Integration. The Cloud Run service receives Firestore document events routed by Eventarc. Key practices: make handlers idempotent — Eventarc guarantees at-least-once delivery, so the same event may arrive more than once. Use specific document path filters (e.g., documents/orders/{orderId}) to avoid triggering on irrelevant writes. Guard against infinite loops — if the Cloud Run service writes back to Firestore, those writes can trigger new events. Use a status field or a separate collection to break the cycle.
Authentication. Firebase Auth is wrapped in an AuthProvider context near the top of the component tree. A custom hook exposes the current user and loading state. A ProtectedRoute wrapper checks authentication before rendering child routes — unauthenticated users are redirected to the login page. Unlike the REST API recipe, auth tokens are not manually attached to requests. The Firebase SDK automatically uses the signed-in user's credentials for Firestore operations, and Security Rules evaluate request.auth accordingly.
State Management. Firestore real-time listeners own all server-derived data. Zustand is reserved for client-only state: sidebar open/closed, theme preference, UI flags. Never duplicate Firestore data into a Zustand store — the Firestore SDK's cache is the source of truth.
Always enforce authorization server-side. Frontend route guards improve UX but are trivially bypassed. The ProtectedRoute pattern prevents UI flicker and accidental navigation — it does not provide security. See Security.
Task Breakdown
| Epic | Description | Ballpark (dev-days) |
|---|---|---|
| Project Setup & Tooling | Scaffold React + TypeScript project, configure Vite, linting, formatting, folder structure, environment config, Firebase SDK initialization, CI pipeline | 2–3 |
| Firebase Configuration | Firebase project setup, Firestore database creation, Security Rules scaffolding, Firebase Emulator Suite configuration for local development | 1–2 |
| Authentication & Authorization | Firebase Auth integration, login/register pages, auth context and protected route wrappers, Security Rules for auth enforcement | 3–5 |
| App Shell & Navigation | Main layout with sidebar/header, routing setup, breadcrumbs, user menu, responsive shell | 2–4 |
| Firestore Data Layer | Typed Firestore converters, per-feature service files, custom hooks for real-time listeners and one-off reads, error handling patterns | 2–4 |
| Security Rules | Granular read/write rules per collection, data validation rules, role-based access, rule unit tests via Emulator | 2–3 |
| CRUD Module 1 (primary entity) | List page with table/search/filters/pagination, detail page, create/edit forms with validation — establishes reusable patterns | 4–6 |
| CRUD Modules 2–3 (additional entities) | Replicate patterns from Module 1 for 2–3 more domain entities (effort drops due to pattern reuse) | 3–6 |
| Eventarc & Cloud Run Backend | Cloud Run service for business logic, Eventarc trigger configuration, idempotency handling, loop prevention, status field pattern | 3–5 |
| Dashboard & Reporting | Summary page with stat cards, 2–3 charts, date/period filters | 3–5 |
| Polish & UX | Loading/error/empty states, offline indicators, toast notifications, responsive tweaks, accessibility basics | 2–4 |
| Testing | Unit tests for critical logic, Security Rules tests (Emulator), component tests, E2E tests for core flows (login, CRUD) | 3–6 |
| Deployment & Infrastructure | Firebase Hosting configuration, SPA rewrites, preview channels, CI/CD pipeline, custom domain | 1–2 |
Ballpark Totals
| Metric | Range |
|---|---|
| Total effort | 31–55 dev-days |
| Duration (1 developer) | 6–11 weeks |
| Duration (2 developers) | 4–6 weeks |
Estimates are slightly higher than the REST API recipe because of additional Firebase-specific work (Security Rules, Eventarc configuration, Emulator setup), even though deployment is simpler.
These are baseline estimates for a straightforward CRUD application with standard complexity. Apply complexity multipliers for drag-and-drop, real-time features, complex forms, i18n, or strict accessibility requirements. Always present estimates as ranges.
What's Not Included
- Cloud Run business-logic domain implementation (the integration scaffold is included above; actual business rules are scoped separately based on domain complexity)
- UX/UI design and Figma work
- Internationalization — add +10–20% if needed
- WCAG accessibility audit — add +15–30%
- Complex data visualizations beyond basic charts
- Firestore data migration or seed data scripts
- Infrastructure provisioning beyond Firebase project setup (Terraform, networking)
- Project management overhead (meetings, demos) — typically +20–30% (see Common Pitfalls)
Deployment Overview
The SPA is deployed as a static site on Firebase Hosting — a global CDN purpose-built for web apps. The Vite build produces static assets that are deployed directly with the Firebase CLI (firebase deploy --only hosting). No container, no nginx, no Cloud Run for the frontend.
SPA routing is configured in firebase.json with a rewrite rule that sends all non-file requests to /index.html so client-side routing works correctly. Firebase Hosting handles HTTPS automatically and serves hashed assets with long-lived cache headers.
Firebase Hosting supports preview channels — isolated, temporary deployments for pull requests. The CI/CD pipeline deploys each PR to a unique preview URL for review, then deploys to the live channel on merge to main.
The Eventarc-triggered Cloud Run service is deployed separately from the SPA. It does not serve user-facing HTTP traffic — it only receives Firestore events. Deploy it in the same region as your Firestore database to minimize event delivery latency. For CI/CD, use a separate workflow step or pipeline for the Cloud Run deployment.
The Firebase Hosting GitHub Action deploys preview channels on PRs and the live site on merge — fully automated with a single workflow step.
Further Reading
Internal docs:
- React SPA on Cloud Run — the REST API alternative to this recipe
- Estimation — techniques and complexity factors
- Requirement Engineering — project requirements checklist
- Design Systems — choosing a design approach
- Rendering — rendering strategy comparison
- Deploy — other hosting options
- Testing Strategy — testing layers and tools
- Recommended Tech Stack — full stack overview
External resources:
- Firebase Hosting documentation
- Cloud Firestore documentation
- Firebase Authentication
- Firebase Security Rules
- Eventarc documentation
- Firebase Emulator Suite
- Firebase App Hosting — for SSR frameworks (Next.js, Angular); not used in this recipe