Platform Detail - Top Performing Posts
This guide explains the Platform Detail feature, which lets users drill into a specific platform from the dashboard to see their top performing posts.
Overview
The dashboard displays platform cards showing aggregate metrics (views, likes, shares) for each connected platform. Users can click any platform card to navigate to a detail view that surfaces their top 5 posts ranked by three criteria:
- Top by Views - Posts with the highest view counts
- Top by Engagement Rate - Posts with the highest engagement rate (
(likes + comments + shares + saves) / max(views, 1) * 100) - Top by Saves - Posts with the most saves (hidden when no posts have any saves)
This gives creators a quick way to understand what content resonates on each platform without manually scrolling through post history.
Supported Platforms
- TikTok
- X (Twitter)
- Threads
- YouTube
User Experience
Navigation Flow
- User opens the dashboard and sees platform ranking cards with aggregate metrics
- User clicks a platform card (e.g., TikTok)
- The app navigates to the platform detail view
- Three ranked lists are displayed: top by views, top by engagement rate, and top by saves
- Each post entry shows a thumbnail, caption preview, and key metrics
Post Entry Display
Each post in the top performing lists includes:
| Field | Description |
|---|---|
| Thumbnail | Post thumbnail image (only shown for video posts that have a thumbnail; text-only posts display no thumbnail) |
| Caption | Truncated post caption (shows "No caption" in italic when unavailable) |
| View count | Total views |
| Like count | Total likes |
| Comment count | Total comments |
| Share count | Total shares |
| Save count | Total saves (pill hidden when 0) |
| Engagement rate | Calculated as (likes + comments + shares + saves) / max(views, 1) * 100 |
| Post link | Direct link to the post on the original platform (card becomes clickable when available) |
Data Sources
Input: Post Metrics
The feature reads from the existing postMetrics collection (users/{userId}/postMetrics/{postId}), which stores current metrics for each tracked post. These metrics are collected by platform-specific metric collectors (see apps/functions/shared/platform-metrics-collector.js and per-platform implementations under apps/functions/integrations/).
Post metadata (caption, thumbnail, post URL, media type) is enriched from two collections with a fallback strategy:
- System posts (
users/{userId}/posts) — matched bypostIdfield against the metric'sexternal_post_id. Providescaption,postURL,thumbnailsarray, andmediaType. - Organic posts (
users/{userId}/organicPosts) — used as a fallback for any posts not found in the system posts collection. Matched byexternal_post_id. Providescaption,share_url, andthumbnail_url.
Enrichment queries use batched where('in', chunk) queries with a maximum of 30 IDs per query to stay within Firestore's in operator limits. Enrichment is best-effort — if it fails, the cron job continues and posts are written without metadata.
Output: Pre-Computed Rankings
Rather than computing rankings on the client at read time, a scheduled Cloud Function pre-computes the top 5 posts for each platform and writes the results to a dedicated Firestore collection:
users/{userId}/topPerformingPosts/{platform}
See the Firestore Data Model for the full schema.
Data Freshness
The top performing posts are recomputed daily by a scheduled Cloud Function:
| Step | Schedule | Description |
|---|---|---|
| Metrics collection | 6 AM UTC daily | Platform-specific collectors fetch latest metrics from each platform's API |
| Top posts computation | 7 AM UTC daily | Cron job reads all postMetrics for each user, ranks posts, and writes results to topPerformingPosts |
The one-hour offset between metrics collection and top posts computation ensures that the ranking job operates on fresh metric data.
Staleness
- Data shown in the platform detail view is at most ~24 hours old
- The
updatedAttimestamp on eachtopPerformingPostsdocument indicates when the rankings were last refreshed
Architecture
flowchart LR
A[Platform APIs] -->|6 AM UTC| B[Metrics Collectors]
B --> C[(postMetrics)]
C -->|7 AM UTC| D[Top Posts Cron Job]
E[(posts)] -->|metadata fallback 1| D
H[(organicPosts)] -->|metadata fallback 2| D
D --> F[(topPerformingPosts)]
F --> G[Platform Detail UI]
I[User clicks Refresh] -->|on-demand| J[HTTP Handler]
J --> D
- Metrics Collectors fetch latest counts (views, likes, comments, shares, saves) from each platform API and write to
postMetrics - Top Posts Cron Job reads all
postMetricsdocuments for a user, enriches with post metadata fromposts(primary) andorganicPosts(fallback), ranks by views/engagement/saves, and writes the top 5 for each platform totopPerformingPosts. ThetopBySavesfield is set tonullwhen no posts on that platform have any saves. - Platform Detail UI reads the pre-computed
topPerformingPosts/{platform}document and renders the ranked lists. The "Top by Saves" section is hidden whentopBySavesis null. - On-Demand Refresh — Users can trigger a manual refresh via the HTTP handler (
topVideosRefreshHandler), which is rate-limited to one refresh per hour per user. The handler writes a pending status totopPerformingPosts/_lastRefreshand enqueues the computation as a Cloud Task.
Engagement Rate Formula
Engagement rate is computed server-side in the cron job:
engagementRate = (likes + comments + shares + saves) / max(views, 1) * 100
The max(views, 1) denominator prevents division by zero for posts with no recorded views.
Saves Handling
The topBySaves field on each topPerformingPosts/{platform} document is nullable:
- When at least one post on the platform has
saveCount > 0,topBySavescontains the top 5 posts sorted by save count descending. - When no posts have any saves,
topBySavesis set tonulland the UI hides the "Top by Saves" section entirely.
On-Demand Refresh
Users can trigger a manual refresh from the platform detail UI. The HTTP handler (apps/functions/http/handlers/top-videos.js) enforces a 1-hour cooldown per user:
- Checks
topPerformingPosts/_lastRefreshfor the user's last refresh timestamp - If within the cooldown window, returns
429withretryAfterSeconds - Otherwise, writes a pending status and enqueues
computeTopPostsForUseras a Cloud Task - Returns
200immediately — computation happens asynchronously
Related Docs
- Firestore Data Model - Collection schemas including
topPerformingPosts