Skip to main content

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.

ConcernThis recipe (Firebase BaaS)REST API recipe
Backend teamMinimal or none — frontend talks to Firestore directlyDedicated backend team building REST endpoints
Real-time dataBuilt-in via Firestore onSnapshotRequires WebSockets or polling on top of REST
Business logicCloud Run triggered asynchronously via EventarcSynchronous REST API endpoints
Data model complexitySimple documents and collections, limited joinsArbitrary SQL/NoSQL with complex queries and joins
Multi-client API surfaceNo formal API contract — clients use SDK directlyShared REST contract consumed by web, mobile, third parties
Offline supportBuilt-in (Firestore IndexedDB cache)Manual implementation (service workers, etc.)
Cost predictabilityPer-operation pricing — can spike with real-time listenersCompute-based pricing — more predictable at scale
Enterprise readinessSee caution belowPublished SLAs for Cloud Run, standard GCP support
Enterprise considerations

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.

DecisionChoice
RenderingClient-side SPA
HostingFirebase Hosting
LanguageTypeScript
UI frameworkReact
Backend integrationFirestore SDK (BaaS)
Backend business logicCloud Run via Eventarc
AuthFirebase Authentication
Primary state modelFirestore real-time listeners + Zustand for client state

Tech Stack

Core

LibraryRole
ReactUI library
TypeScriptType-safe JavaScript
ViteBuild tool and dev server

Styling & UI

LibraryRole
Tailwind CSSUtility-first CSS
shadcn/uiComponent collection (Radix + Tailwind)

State & Data

LibraryRole
Firebase JS SDK (firebase/firestore, firebase/auth)Firestore data access, auth, real-time listeners
ZustandClient-only state management
Why no TanStack Query?

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

LibraryRole
React RouterClient-side routing
React Hook FormForm state management
ZodSchema validation
date-fnsDate utilities
SonnerToast notifications

Build & Testing

LibraryRole
VitestUnit and integration testing
PlaywrightEnd-to-end testing
tip

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.

Security Rules are your API boundary

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.

caution

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

EpicDescriptionBallpark (dev-days)
Project Setup & ToolingScaffold React + TypeScript project, configure Vite, linting, formatting, folder structure, environment config, Firebase SDK initialization, CI pipeline2–3
Firebase ConfigurationFirebase project setup, Firestore database creation, Security Rules scaffolding, Firebase Emulator Suite configuration for local development1–2
Authentication & AuthorizationFirebase Auth integration, login/register pages, auth context and protected route wrappers, Security Rules for auth enforcement3–5
App Shell & NavigationMain layout with sidebar/header, routing setup, breadcrumbs, user menu, responsive shell2–4
Firestore Data LayerTyped Firestore converters, per-feature service files, custom hooks for real-time listeners and one-off reads, error handling patterns2–4
Security RulesGranular read/write rules per collection, data validation rules, role-based access, rule unit tests via Emulator2–3
CRUD Module 1 (primary entity)List page with table/search/filters/pagination, detail page, create/edit forms with validation — establishes reusable patterns4–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 BackendCloud Run service for business logic, Eventarc trigger configuration, idempotency handling, loop prevention, status field pattern3–5
Dashboard & ReportingSummary page with stat cards, 2–3 charts, date/period filters3–5
Polish & UXLoading/error/empty states, offline indicators, toast notifications, responsive tweaks, accessibility basics2–4
TestingUnit tests for critical logic, Security Rules tests (Emulator), component tests, E2E tests for core flows (login, CRUD)3–6
Deployment & InfrastructureFirebase Hosting configuration, SPA rewrites, preview channels, CI/CD pipeline, custom domain1–2

Ballpark Totals

MetricRange
Total effort31–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.

caution

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.

tip

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:

External resources: