Overview
Display view counts publicly on blog posts to show popularity and add social proof. This will make the blog more engaging by showing readers which articles are most popular.
Status
Open - Ready to implement
Priority
Medium
Requirements
Functional Requirements
- Display view count on individual blog post pages
- Display view count on blog post cards (list view)
- Format numbers in a readable way (e.g., "1.2k", "5k", "100")
- Cache view counts to avoid performance issues
- Update counts periodically (not real-time)
- Show "views" label with the count
- Handle zero views gracefully (don't show or show "0 views")
- Prevent view count manipulation (basic protection)
Non-Functional Requirements
- Fast page load (view count shouldn't block rendering)
- No additional database queries per page if possible
- Accurate counts (within caching period)
- Mobile-friendly display
- Accessible (screen reader friendly)
Proposed Solution
Data Structure
Add to Posts collection fields:
{
name: 'viewCount',
type: 'number',
defaultValue: 0,
admin: {
readOnly: true, // Updated by analytics system only
condition: (data) => data.status === 'published'
}
}
{
name: 'lastViewedAt',
type: 'date',
admin: {
readOnly: true,
hidden: true // Only for system use
}
}
Display Locations
1. Blog Post Cards
Location: components/posts/post-card.tsx
Display in the card footer or metadata section:
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Calendar className="h-3 w-3" />
<span>{post.readingTime}m</span>
<span>•</span>
<Eye className="h-3 w-3" />
<span>{formatViewCount(post.viewCount)} views</span>
</div>
2. Blog Post Detail Page
Location: app/(frontend)/blog/[slug]/page.tsx
Display in the header metadata section.
Number Formatting
Create a utility function formatViewCount() to format large numbers:
- 0 → "0"
- 5 → "5"
- 100 → "100"
- 1500 → "1.5k"
- 15000 → "15k"
- 150000 → "150k"
- 1500000 → "1.5M"
Caching Strategy
Option 1: Server-Side Caching (Recommended)
Cache view counts for a period (1-5 minutes) to avoid excessive database reads.
Option 2: Incremental Counter
Update view count asynchronously without blocking page load using React Query.
Option 3: Payload Hook
Use Payload beforeRead hook to increment views on frontend reads only.
Anti-Manipulation Measures
- Session-based tracking: Only count one view per session per post
- Rate limiting: Limit increments from same IP
- Bot detection: Don't count known bots (check user agent)
- Time threshold: Require minimum time on page (e.g., 5 seconds)
// components/view-tracker.tsx
'use client'
import { useEffect } from 'react'
import { trackView } from '@/app/actions/analytics'
export function ViewTracker({ postId }: { postId: string }) {
useEffect(() => {
const viewed = sessionStorage.getItem(`viewed_${postId}`)
if (viewed) return
sessionStorage.setItem(`viewed_${postId}`, Date.now().toString())
const timeout = setTimeout(() => {
trackView({ postId })
}, 5000)
return () => clearTimeout(timeout)
}, [postId])
return null
}
Display Variations
Minimal Style (Recommended)
With Icon
👁 125 views
With Label
125 views • 5 min read
Popular Badge
Show "Popular" badge for posts with high view counts:
{post.viewCount > 1000 && (
<Badge variant="default" className="font-mono">
🔥 Popular
</Badge>
)}
Implementation Plan
Phase 1: Data Layer (1 day)
- Add
viewCount and lastViewedAt fields to Posts collection
- Create
formatViewCount utility function
- Implement caching strategy
- Add view tracking server action
Phase 2: UI Integration (1 day)
- Add view count to blog post cards
- Add view count to blog post detail pages
- Style to match terminal aesthetic
- Add icon and formatting
Phase 3: Anti-Manipulation (Optional)
- Implement session tracking
- Add time threshold (5 seconds)
- Add bot detection
Phase 4: Enhancements (Optional)
- Add "Popular" badge for high-view posts
- Add sorting by views in blog listing
- Add "Most Viewed" section
- Add view count trend (↑ ↓)
Performance & Privacy
Performance:
- Cache aggressively (view counts don't need to be real-time)
- Don't block rendering (load counts asynchronously)
- Batch updates if high traffic
- Use CDN cache
Privacy:
- View counts are aggregate data (no PII)
- No need for cookie consent
- GDPR compliant by design
Accessibility
- Screen reader announcement: "125 views"
- Icon with aria-label: "View count"
- Don't use color alone to convey popularity
- Maintain sufficient contrast
Dependencies
Related Issues
Refer to docs/issues/public-view-count.md for full details.
Overview
Display view counts publicly on blog posts to show popularity and add social proof. This will make the blog more engaging by showing readers which articles are most popular.
Status
Open - Ready to implement
Priority
Medium
Requirements
Functional Requirements
Non-Functional Requirements
Proposed Solution
Data Structure
Add to Posts collection fields:
Display Locations
1. Blog Post Cards
Location:
components/posts/post-card.tsxDisplay in the card footer or metadata section:
2. Blog Post Detail Page
Location:
app/(frontend)/blog/[slug]/page.tsxDisplay in the header metadata section.
Number Formatting
Create a utility function
formatViewCount()to format large numbers:Caching Strategy
Option 1: Server-Side Caching (Recommended)
Cache view counts for a period (1-5 minutes) to avoid excessive database reads.
Option 2: Incremental Counter
Update view count asynchronously without blocking page load using React Query.
Option 3: Payload Hook
Use Payload
beforeReadhook to increment views on frontend reads only.Anti-Manipulation Measures
Display Variations
Minimal Style (Recommended)
With Icon
👁 125 viewsWith Label
125 views • 5 min readPopular Badge
Show "Popular" badge for posts with high view counts:
Implementation Plan
Phase 1: Data Layer (1 day)
viewCountandlastViewedAtfields to Posts collectionformatViewCountutility functionPhase 2: UI Integration (1 day)
Phase 3: Anti-Manipulation (Optional)
Phase 4: Enhancements (Optional)
Performance & Privacy
Performance:
Privacy:
Accessibility
Dependencies
EyeorUsers)Related Issues
Refer to
docs/issues/public-view-count.mdfor full details.