Performance settings
The Performance section in controls how the admin queries the posts collection in Firestore. There's one toggle and it has real implications for sites with thousands of posts.
The Performance section in /settings/general controls how the admin queries the posts collection in Firestore. There's one toggle and it has real implications for sites with thousands of posts.
Pagination mode
Two modes:
global (default)
A single live subscription on the entire posts collection. The admin keeps every post in memory in CmsDataContext and runs filters / pagination / counts client-side. No composite indexes required — works on a fresh Firestore project.
Pros:
- Real-time updates — every post list reflects changes from other admins instantly
- No index setup — works the moment you create the Firestore database
- In-memory counts — bulk-action selection counts are free
- Search is fast — search runs over the in-memory corpus, no extra reads
Cons:
- Memory cost scales with post count — at 5 000+ posts, the in-memory list weighs down the admin
- Initial load reads every post — first admin login pays the round-trip cost (~5-10 s for 5 000 posts on a typical connection)
paginated
Cursor-paginated subscriptions, one page at a time. The admin only fetches the posts visible on the current page. Counts go through Firestore aggregation queries (single-read each).
Pros:
- Memory cost stays constant regardless of total post count
- Initial load is O(page size) — fast even with 50 000 posts
Cons:
- Composite indexes required — without them, paginated queries fail with
failed-precondition - Selection is more involved — bulk actions span pages, requiring the admin to fall back to one-shot fetches when resolving cross-page selections
- Real-time updates only on the current page — changes elsewhere don't push automatically
Which mode should I use?
| Site size | Recommendation |
|---|---|
| 0-5 000 posts | global (default) |
| 5 000-50 000 posts | paginated (after creating composite indexes) |
| 50 000+ posts | paginated strongly recommended; consider Firestore aggregation patterns for stats |
If unsure, start with global and switch if you hit memory pressure.
Switching modes
In /settings/general → Performance → toggle Paginated mode → Save.
The admin re-renders against the new mode immediately. No data migration needed.
When switching to paginated, the FirestoreSetupGate appears on the next list-view load if composite indexes aren't created yet. It pings the two required queries and surfaces one-click create links to the Firebase Console when they fail with failed-precondition.
Composite indexes (paginated mode only)
Two composite indexes on the posts collection are mandatory in paginated mode:
| Fields | Order |
|---|---|
type |
ASC |
createdAt |
DESC |
| Fields | Order |
|---|---|
type |
ASC |
status |
ASC |
createdAt |
DESC |
The first covers the All tab; the second covers Draft / Online filtered tabs.
How to create them
Three options:
- Via the in-app FirestoreSetupGate — quickest. The gate detects the missing indexes on first paginated query, shows you a one-click link per index that opens the Firebase Console with the right fields pre-filled.
- Via
firebase deploy— commit afirestore.indexes.jsonto your project and runfirebase deploy --only firestore:indexes. - Via gcloud CLI —
gcloud firestore indexes composite create .... Documented in the README.
Index creation takes 1-15 minutes depending on collection size. While the index builds, the gate keeps showing the setup screen with a "build in progress" state.
Auto-detection caching
Once the gate verifies both indexes exist, it caches that result in localStorage.flexweg.firestoreIndexesReady = "1" so subsequent loads skip the ping. The cache is invalidated on every /settings/general save (so flipping global ↔ paginated re-pings against fresh truth).
Other performance considerations
The mode toggle is the only Performance setting. Other knobs (image variant generation, regeneration throttling) live elsewhere:
- Image variant generation — controlled by the active theme's
imageFormats. Each format adds one resize pass per upload. - Regeneration throttle —
regenerateAllpaces uploads at 75 ms between calls (hardcoded, not user-tunable). Adjust by editingsrc/services/publisher.tsif you have a special-case need.