Capstone: Integrating the Catalogue Into the Existing Astro Site, and Self-Managed MongoDB vs. Managed Firestore Compared

Personal Catalogue: React & Firebase

Chapter 10 · Capstone

Chapter 9 got the whole app running as a real, standalone Firebase Hosting deployment. This closing chapter puts it somewhere the user actually visits — a real path under their own existing Astro-based site — lays out a genuine, concrete plan for the one feature deferred since Chapter 1, and closes the whole course with an honest, itemized scorecard against its own MongoDB sibling.

Three Real Ways to Mount This — With Firebase Hosting Changing the Menu Slightly

OptionWhat it involvesTradeoff
Subdomain (catalogue.existing-site.com)Point a DNS record straight at Firebase Hosting via its own custom-domain feature — no reverse proxy needed at allSimplest possible setup; feels like a separate site rather than one integrated experience
Path-mounted reverse proxy (existing-site.com/catalogue/)One nginx location block on the existing site's own domain, proxying that one path to Firebase Hosting's real URLFeels like part of the same site; needs the real fixes this chapter builds — covered below
A deeper Astro-native rewriteKeep only Firestore's own schema and Security Rules; rebuild the UI as new Astro pages/components calling the Firestore SDK directlyThe deepest real integration and the most rework — appropriate only if Astro should become the one true frontend long-term

The path-mounted option is built out in full here, genuinely integrated without discarding any of the nine chapters of real React frontend already built.

The Real Asset-Path Fix — Identical to the MongoDB Sibling, for the Identical Reason

Chapter 9's own npm run build produces an index.html referencing its own JS and CSS bundles with absolute paths like /assets/index-abc123.js. That's correct for a standalone deployment at a domain root, and genuinely wrong once mounted at /catalogue/: the browser's own subsequent request for that absolute path resolves against the domain's own root, not the real sub-path this app actually lives under — a request for /assets/index-abc123.js never even reaches nginx's own /catalogue/ location block, since the request itself doesn't start with that prefix at all. This is a pure client-side, browser-resolves-absolute-paths problem, with nothing about Firebase Hosting or Firestore changing it in any way. The fix is the identical one the MongoDB sibling verified against Vite's own documentation:

// vite.config.js export default defineConfig({ plugins: [react()], base: '/catalogue/', });

Rebuilt with this in place, index.html references /catalogue/assets/index-abc123.js instead — a path nginx's own /catalogue/ location block genuinely matches, and one Firebase Hosting itself never needs to know anything special about, since it just serves whatever file that path resolves to inside its own dist/ upload.

The Runtime Fix the MongoDB Sibling Needed — and This Course Genuinely Doesn't

The MongoDB sibling's own second fix updates VITE_API_URL to a real sub-path value, because its own fetch calls are built as relative paths that resolve against whatever origin and path the current page happens to be served from — the browser has no way to know an API call meant for /catalogue/api/items shouldn't just go to /api/items unless it's told so explicitly.

This App Has No Equivalent Runtime Fix to Make
The Firestore client SDK never builds a relative, current-page-based URL for anything. Every read and write goes directly to Firebase's own real, fixed backend endpoints (keyed by the projectId set back in Chapter 9's own environment variables), completely independent of whatever domain or path the React app itself happens to be served from at that moment. Mounting this app at /catalogue/, at a bare subdomain, or at a completely different domain entirely changes nothing about how Firestore calls resolve — there was never a same-origin-relative API call here for a sub-path to break in the first place, a direct, concrete extension of Chapter 9's own "no CORS needed" finding.

Reverse-Proxying to an External, CDN-Fronted Backend — a Genuinely Different Nginx Problem

The MongoDB, PHP, and Django siblings all proxy to a local process on the same machine — proxy_pass http://localhost:PORT/;, with no real concern about which domain name the request claims to be for, since a local process doesn't route based on that. Firebase Hosting is a shared, multi-tenant CDN serving many different Firebase projects from the same infrastructure, and it decides which project's own content to serve based on the Host header (and, for HTTPS, SNI) presented on the request:

# /etc/nginx/sites-available/existing-site (excerpt) server { listen 443 ssl; server_name existing-site.com; # ...the existing Astro site's own location blocks, unchanged... location /catalogue/ { proxy_pass https://catalogue-prod.web.app/; proxy_set_header Host catalogue-prod.web.app; proxy_ssl_server_name on; } }
Two Lines That Genuinely Aren't Optional Here
Naively passing through the original Host: existing-site.com header (nginx's own real default when none is set explicitly) would confuse Firebase's own routing entirely — it has no project registered under that hostname, so the request would fail or resolve to the wrong content. Explicitly rewriting Host to the real Firebase Hosting domain fixes that. proxy_ssl_server_name on; is the second, easy-to-miss piece: it makes nginx present the correct SNI hostname during the TLS handshake itself, which the same shared, multi-tenant infrastructure also depends on to route the connection to the right backend before a single byte of the actual HTTP request is ever read.

With both in place, the trailing slash on location /catalogue/ and proxy_pass https://catalogue-prod.web.app/; strips the /catalogue prefix before forwarding, exactly the same mechanism the MongoDB sibling relies on — a request for /catalogue/assets/index-abc123.js reaches Firebase Hosting as a request for exactly /assets/index-abc123.js, which its own CDN already serves correctly with no changes needed on the Firebase Hosting side at all.

Barcode Scanning — Where This Course's Own "No Backend" Streak Finally Ends

Chapter 1 deferred barcode scanning explicitly, and flagged an honest, unresolved question rather than asserting an answer: whether this course could get away with never needing a Cloud Function at all. With the app actually live, that question has a real answer now — no, not once this specific feature is added.

  • Camera access — a real, actively maintained library like html5-qrcode reads a barcode from the device camera directly in the browser, matching the MongoDB sibling's own recommendation exactly, since this piece is pure frontend and has nothing to do with either backend.
  • Book lookup, and the real reason it needs a Cloud Function — a scanned ISBN can be sent to Open Library's real, free, no-API-key endpoint to auto-fill title, author, and publisher. Calling it directly from the browser would work today, but the moment this app ever needs a real, secret-holding API key for any lookup service, that call has to move server-side — and since this course has no server of its own at all, its first-ever onCall Cloud Function is what that server-side call would actually become, the exact scenario Chapter 1's own honest open question was waiting to be resolved by.
  • Cd/Dvd/Bluray lookup — honestly still open, the same real gap the MongoDB sibling names: no single, universally reliable free UPC-lookup service has actually been verified for physical media here. Genuine research is the concrete next step, not something to guess at.

Chapter Attribution

Piece of the finished appBuilt in
Firestore document shapes, matched to the MongoDB sibling's own schemaChapter 2
The client-SDK-only architecture decision, plus real Security RulesChapter 3
Add-item form, driven by the shared FIELD_CONFIGChapter 4
Tag editing via arrayUnion/arrayRemove, plus the tagsOnlyOnBooks ruleChapter 5
Fetch-and-filter search, replacing regex entirelyChapter 6
List and detail views, driven by a live onSnapshot listenerChapter 7
Friction reduction: focus return, debounced duplicate checkChapter 8
Firebase Hosting deployment, env-driven project configChapter 9
Path-mounted reverse proxy, Vite base, barcode-scanning planChapter 10 (this chapter)

Self-Managed MongoDB vs. Managed Firestore: the Real Scorecard

AreaReal outcome across this course
Backend of any kindFirestore wins — no server to write, run, or deploy at all (Ch.3, Ch.9)
Per-type schema validationMongoDB wins — Mongoose discriminators enforce it for free; this course hand-built validateItem() and Security Rules to match (Ch.2, Ch.3)
Atomic array updates (tags)A genuine tie — arrayUnion/arrayRemove match $addToSet/$pull exactly, with no backend route needed on either side to get there (Ch.5)
Preventing a field on the wrong document typeMongoDB wins — automatic via discriminator models; this course found the gap the hard way and closed it with a hand-written rule (Ch.5)
Text searchMongoDB wins clearly — real regex/$or search vs. no substring search capability at all here, worked around with a client-side fetch-and-filter (Ch.6)
Keeping a UI in sync with the databaseFirestore wins — onSnapshot() eliminates the manual state-lifting wiring this course's own sibling needs (Ch.7)
Production deployment complexityFirestore wins decisively — no CORS, PM2, or reverse proxy for a backend of its own (Ch.9)
Ongoing operating cost modelMongoDB wins on predictability — a self-run instance has no per-document-read billing; every Firestore read is metered, made concrete in Chapter 6's own full-collection search cost
Sub-path deployment fixes neededFirestore needs fewer — only the shared Vite asset-path fix, not a second runtime API-path fix, since Firestore never routes through the app's own current origin (Ch.10)

Neither column wins outright. What genuinely changed, chapter after chapter, wasn't whether real problems existed — it's where the work to solve them landed: inside a schema class and an Express route for the MongoDB sibling, or inside a hand-written validator and a declarative rules file here. Managed infrastructure removed real operational work this course never had to think about; it also removed guarantees this course had to go rebuild by hand, one genuine gap at a time.

Hands-On Exercises

Exercise 1

Set base: '/catalogue/' in vite.config.js, rebuild, and confirm the built index.html's own asset references correctly include the /catalogue prefix — then explain in one sentence why no equivalent runtime environment-variable fix is needed for Firestore calls, unlike the MongoDB sibling's own second fix.

📄 View solution
Exercise 2

Configure the nginx location /catalogue/ block against a real, deployed Firebase Hosting site (a second local nginx config is fine for testing), including the Host header override and proxy_ssl_server_name. Confirm the page loads correctly, then temporarily remove just the Host header line and observe what happens.

📄 View solution
Exercise 3

Using this chapter's own scorecard, pick the single real finding from the whole course you think matters most for a genuinely larger catalogue (tens of thousands of items rather than a few hundred), and explain why — citing the specific chapter it came from.

📄 View solution

Chapter 10 Quick Reference — Course Complete

  • Three mounting options — a Firebase Hosting custom domain, a path-mounted reverse proxy (built here), or a deeper Astro-native rewrite
  • The asset-path fix — identical to the MongoDB sibling's own vite.config base fix, for the identical browser-side reason
  • The runtime fix that isn't needed — Firestore never routes through the app's own current origin, so there's no second, API-path-specific fix to make
  • A genuinely new nginx problem — proxying to a CDN-fronted external service needs an explicit Host header override and proxy_ssl_server_name, unlike proxying to a local process
  • Barcode scanning — the real point where this course's own "no backend" streak ends, needing its first Cloud Function
  • The scorecard — real, itemized wins and costs across both variants; no clean overall winner, just different places the real work landed
  • This closes the course — 10/10 chapters, matching the shared four-course Personal Catalogue spec from Chapter 1