Skip to main content

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
  • Instagram
  • X (Twitter)
  • Facebook
  • Threads
  • YouTube
  • LinkedIn

User Experience

  1. User opens the dashboard and sees platform ranking cards with aggregate metrics
  2. User clicks a platform card (e.g., TikTok)
  3. The app navigates to the platform detail view
  4. Three ranked lists are displayed: top by views, top by engagement rate, and top by saves
  5. Each post entry shows a thumbnail, caption preview, and key metrics

Post Entry Display

Each post in the top performing lists includes:

FieldDescription
ThumbnailPost thumbnail image (only shown for video posts that have a thumbnail; text-only posts display no thumbnail)
CaptionTruncated post caption (shows "No caption" in italic when unavailable)
View countTotal views
Like countTotal likes
Comment countTotal comments
Share countTotal shares
Save countTotal saves (pill hidden when 0)
Engagement rateCalculated as (likes + comments + shares + saves) / max(views, 1) * 100
Post linkDirect 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:

  1. System posts (users/{userId}/posts) — matched by postId field against the metric's external_post_id. Provides caption, postURL, thumbnails array, and mediaType.
  2. Organic posts (users/{userId}/organicPosts) — used as a fallback for any posts not found in the system posts collection. Matched by external_post_id. Provides caption, share_url, and thumbnail_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:

StepScheduleDescription
Metrics collection6 AM UTC dailyPlatform-specific collectors fetch latest metrics from each platform's API
Top posts computation7 AM UTC dailyCron 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 updatedAt timestamp on each topPerformingPosts document 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
  1. Metrics Collectors fetch latest counts (views, likes, comments, shares, saves) from each platform API and write to postMetrics
  2. Top Posts Cron Job reads all postMetrics documents for a user, enriches with post metadata from posts (primary) and organicPosts (fallback), ranks by views/engagement/saves, and writes the top 5 for each platform to topPerformingPosts. The topBySaves field is set to null when no posts on that platform have any saves.
  3. Platform Detail UI reads the pre-computed topPerformingPosts/{platform} document and renders the ranked lists. The "Top by Saves" section is hidden when topBySaves is null.
  4. 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 to topPerformingPosts/_lastRefresh and 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, topBySaves contains the top 5 posts sorted by save count descending.
  • When no posts have any saves, topBySaves is set to null and 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:

  1. Checks topPerformingPosts/_lastRefresh for the user's last refresh timestamp
  2. If within the cooldown window, returns 429 with retryAfterSeconds
  3. Otherwise, writes a pending status and enqueues computeTopPostsForUser as a Cloud Task
  4. Returns 200 immediately — computation happens asynchronously