Deployment

Food Tracker (React + Firebase)

Chapter 12 · Deployment

Four genuinely different things need to reach production: the React app itself, three Cloud Functions, Firestore's Security Rules, and a composite index that's only ever existed as a console click-through so far.

Building and Configuring Hosting

npm run build produces a static output folder; firebase.json tells Hosting where to find it and how to handle client-side routes:

{ "hosting": { "public": "dist", "rewrites": [{ "source": "**", "destination": "/index.html" }] } }

The rewrites rule is not optional decoration — every route that isn't the root path only exists as far as React Router (running inside index.html) understands it. Hosting itself has no file at /history or /scan; without this rewrite, refreshing or directly navigating to any route but the root returns a genuine 404.

Deploying — All of It, or Just What Changed

firebase deploy # everything firebase deploy --only hosting # just the React build firebase deploy --only functions # just the Cloud Functions firebase deploy --only firestore:rules # just Chapters 6/9/11's own rules firebase deploy --only firestore:indexes # just the composite index — see below

Scoped deploys aren't just a shortcut — redeploying Hosting when only a Cloud Function changed adds time and risk for no reason. Deploying only what actually changed keeps each deploy small, fast, and easy to reason about.

Codifying Chapter 7's Own Index

Chapter 7's composite index was created by clicking the link in Firestore's own error message — fine for one developer's local project, but a fresh environment, a teammate's machine, or a CI pipeline has no such link to click. firestore.indexes.json, checked into source control, makes that index a deployable artifact instead of a one-time manual step:

{ "indexes": [ { "collectionGroup": "items", "fields": [ { "fieldPath": "status", "order": "ASCENDING" }, { "fieldPath": "expiryDate", "order": "ASCENDING" } ] } ] }

Config for Secrets — Not Needed Yet, Worth Knowing

Neither Open Food Facts nor TheMealDB has ever needed a key in this course — but if that ever changed (a paid nutrition API, say), firebase-functions/params' defineSecret is where a real key would live, kept entirely out of source control and out of the client:

const { defineSecret } = require("firebase-functions/params"); const nutritionApiKey = defineSecret("NUTRITION_API_KEY"); // firebase functions:secrets:set NUTRITION_API_KEY
Chapter 4's requirement, finally genuinely met
Chapter 4 noted that getUserMedia only works over HTTPS or localhost, and left it at that since local development doesn't need to worry about it yet. Firebase Hosting serves every deployment over HTTPS by default, on both its own *.web.app domain and any custom domain attached later. This chapter is where that early requirement stops being an assumption and becomes something the deployed app actually satisfies.
Preview channels before going live
firebase hosting:channel:deploy preview publishes the current build to a separate, temporary URL — genuinely useful for trying a change (or sharing it for feedback) before merging it into the production channel everyone else sees.
Forgetting the rewrite rule is the single most common first-deploy mistake
Without the rewrites block above, the app works perfectly at its root URL and then appears completely broken the moment someone refreshes on any other page, or shares a direct link to one — a confusing, easy-to-miss failure mode for anyone deploying a single-page app to Firebase Hosting for the first time.

Where This Course Is Headed

One chapter left: a capstone tying together this course's own thread — from Chapter 1's architectural framing through this chapter's own deployment — into one complete, working, per-user Food Tracker.

Hands-On Exercises

Exercise 1

Explain what the Hosting rewrite rule actually does, and describe precisely what breaks for a user if it's left out.

📄 View solution
Exercise 2

Explain why deploying only what changed (a scoped `firebase deploy --only ...`) is preferable to running a full `firebase deploy` every time, even though the full deploy would technically also work.

📄 View solution
Exercise 3

Explain why Chapter 7's composite index needs to be codified in firestore.indexes.json for a real deployment, rather than just clicking the console link once the way it was created during development.

📄 View solution

Chapter 12 Quick Reference

  • Hosting rewrite rule — every route rewrites to index.html, or non-root routes 404 on direct navigation/refresh
  • Scoped deploys--only hosting/functions/firestore:rules/firestore:indexes, faster and lower-risk than deploying everything every time
  • firestore.indexes.json — codifies Chapter 7's console-created index so new environments don't need the manual click-through
  • defineSecret — where a real API key would live, if this app's external APIs ever required one
  • HTTPS by default — Firebase Hosting finally, genuinely satisfies Chapter 4's own getUserMedia requirement
  • Next chapter: Capstone — A Complete, Working Food Tracker