Auto-increment field on document fetch to track article views in Sanity

15 replies
Last updated: Nov 30, 2025
Hello, I’m currently stuck at one point and I’m wondering if there is a way for autoincrement a value when a document gets fetched? For example to get the most read articles of a blog? Without getting into race condition? Thanks for your help
AI Update

Tracking view counts or any auto-incrementing counter when a document is fetched is a tricky problem, especially when you need to avoid race conditions. Let me explain the best approaches for this in Sanity:

The Challenge

Incrementing a counter on every document fetch creates a race condition problem - if multiple users fetch the same document simultaneously, some increments could be lost. This is because read-and-update operations aren't atomic.

The modern approach is to use Sanity Functions to track views server-side. Instead of incrementing on fetch (which happens client-side), you can:

  • Have clients trigger a view event (via a simple API call to your Function)
  • Use a Sanity Function to handle the increment server-side
  • Use the inc() patch operation which is designed to handle concurrent updates

Here's how the increment operation works with the Sanity client:

client
  .patch('document-id')
  .inc({views: 1}) // Atomically increment by 1
  .commit()

The .inc() operation is specifically designed to handle race conditions - it performs atomic increments that won't lose counts even with concurrent updates.

Example with Sanity Functions:

// In your Sanity Function (sanity.blueprint.ts)
export default defineBlueprint({
  functions: [
    defineFunction({
      name: 'track-view',
      handler: async (event) => {
        const {articleId} = event.body
        
        await client
          .patch(articleId)
          .inc({viewCount: 1})
          .commit()
          
        return {success: true}
      }
    })
  ]
})

// In your frontend
async function trackView(articleId) {
  await fetch('https://your-project.api.sanity.io/v2025-01-01/functions/track-view', {
    method: 'POST',
    body: JSON.stringify({ articleId })
  })
}

2. Aggregate Analytics Separately

For production use cases, consider tracking views in a separate analytics system (like Google Analytics, Plausible, or a custom service) rather than in your content documents. This approach:

  • Keeps your content lake clean and focused on content
  • Provides better performance (no write operations on every view)
  • Offers more sophisticated analytics capabilities
  • Avoids the race condition problem entirely

3. Batch Updates with Scheduled Functions

If you need the data in Sanity, you could:

  1. Log view events to a separate system or queue
  2. Use a Sanity Function on a schedule to batch-update view counts periodically
  3. This reduces write operations and makes race conditions less likely

Important Considerations

  • Don't increment on fetch queries: Running mutations every time someone queries content will hurt performance and could hit rate limits
  • CDN caching: If you're using Sanity's CDN (useCdn: true), your queries are cached, so you wouldn't even know about most views
  • Client-side tracking is unreliable: Users can block tracking, and you can't trust client-side data
  • Separate concerns: Keep the "fetch content" action separate from the "track view" action

Safe Implementation Pattern

// When a user views an article
async function trackView(articleId) {
  // Call your Sanity Function (or own API endpoint)
  await fetch('/api/track-view', {
    method: 'POST',
    body: JSON.stringify({ articleId })
  })
}

// In your API endpoint or Sanity Function
async function incrementViewCount(articleId) {
  await client
    .patch(articleId)
    .inc({viewCount: 1}) // Atomic increment - race condition safe!
    .commit()
}

// Fetch content separately - no mutations here
async function getArticle(articleId) {
  return await client.fetch(
    `*[_type == "post" && _id == $id][0]`,
    {id: articleId}
  )
}

The key takeaway: use the atomic .inc() operation for incrementing, and separate your view tracking from content fetching. This ensures you won't lose counts even with concurrent updates, and you maintain good performance for your content queries.

Show original thread
15 replies

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?