The Utility section of my internal PWA had a problem: the file catalog was hardcoded in the HTML. Every time a file was added or removed, it required a code commit and a new deploy. That was clearly unsustainable.
In this session I replaced the static catalog with a live one driven by Firebase Realtime Database and Firebase Storage — and added an admin modal to manage everything at runtime, without touching the code.
Full article (bilingual IT/EN): roversia.it — Dynamic file catalog with Firebase RTDB + Storage
The architecture
Two Firebase services with distinct roles:
-
Realtime DB (node
utility_catalog): holds the catalog structure — sections, file names, download URLs, file types. This is the single source of truth. -
Firebase Storage (folder
Utility/): holds the actual files. On upload, a public URL is generated and written back to the DB node.
The old hardcoded array wasn't deleted entirely — it became a _UTIL_CATALOG_FALLBACK constant. If the RTDB node doesn't exist yet (first run), the app seeds Firebase automatically. Zero manual migration.
Role-based admin modal
The edit button is only rendered for users with the admin role. The check happens client-side against a Firebase Auth custom claim, and is also enforced at the DB rules level.
The modal itself has three sections: add a new file (upload + metadata), edit an existing entry, delete with confirmation. All writes go to both Storage (the file) and RTDB (the metadata record) in sequence.
The async race condition
The catalog loads on DOMContentLoaded via a onValue listener. But the admin role check depends on getIdTokenResult() — which resolves asynchronously after the catalog render.
Result: the edit button was sometimes not injected because the role check resolved after the UI had already been painted.
Fix: I moved the button injection inside the .then() of getIdTokenResult(), so it only runs once the role is confirmed. A simple sequencing fix, but it took a while to spot because the bug was intermittent (timing-dependent).
The Service Worker cache bug
After deploying the new dynamic catalog, returning users were still seeing the old hardcoded version. The Service Worker was caching the old index.html and serving it from cache, ignoring the new deploy.
The fix was bumping the cache version string in sw.js:
const CACHE_NAME = 'panelcontrol-v4'; // was v3
On the next visit, the SW detects the new cache name, deletes the old cache, and fetches fresh assets. Standard stuff — but easy to forget when you're focused on the feature, not the infrastructure.
Key takeaways
- RTDB as catalog source of truth is lightweight and real-time. No backend needed for reads.
- Async sequencing matters: anything that depends on auth state must run after the auth promise resolves, not alongside it.
- Service Worker versioning: always bump the cache name on significant deploys, or users will be stuck on stale assets longer than expected.
The full session walkthrough — including the complete modal code and the RTDB structure — is on my blog:
👉 roversia.it — Dynamic file catalog with Firebase RTDB + Storage
Top comments (0)