Skip to content

feat: Add Public View Count Display for Blog Posts #3

@codewizdave

Description

@codewizdave

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

  1. Display view count on individual blog post pages
  2. Display view count on blog post cards (list view)
  3. Format numbers in a readable way (e.g., "1.2k", "5k", "100")
  4. Cache view counts to avoid performance issues
  5. Update counts periodically (not real-time)
  6. Show "views" label with the count
  7. Handle zero views gracefully (don't show or show "0 views")
  8. 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

  1. Session-based tracking: Only count one view per session per post
  2. Rate limiting: Limit increments from same IP
  3. Bot detection: Don't count known bots (check user agent)
  4. 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)

125 views

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions