Incrementing values in Sanity for analytics purposes

15 replies
Last updated: May 30, 2022
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.

I would personally recommend not using Sanity as an analytics platform. You might want to use a different platform more suited to collect real-time data without putting a toll on your Sanity usage.
That’s sound advice from
user F

Buuuut, if you want to do it anyways, because there is not
that much traffic, and it’s a fun way to learn the platform. Let’s dive into it:
Because of the way we tackle drafts, I usually do things like this by making a separate document that refers to the blog post. You can use the
.inc()
method in the JS client to increment a value. and you should be able to do so in just a few lines of code in a serverless function.
Provided you go ahead with it, I would also agree to decouple the analytics data from the document itself by storing it in a different document type that references your main document, as Knut explained. 🙂
Thanks for your reply, I’m not planning to use Sanity as Analytics tool, but in some cases it would be very handy. For example if you want to recommend the moste read articles at the end of the current article. if that makes sense. It would be easier to have this data already in sanity, instead of connecting to a other endpoint for this information. Thanks Knut, i will look into it
Right, that makes sense. I think the way I would approach that would be by backporting some normalized data from my analytics tool (GA, Plausible…) via a CRON job. Like every week, I would increment the relevant field for each article as Knut suggested, based on data extracted from the analytics tool.
If you truly want the most read articles, then you should only trigger it when they reach the bottom. Then I wouldn’t be too worried about API usage 😛 (unless you have a fairly successful blog!)
True, great point. 🙂
Ok thanks, thats are great tips! I like the idea of incrementing only at the end of the article. I don't know how much is much for sanity 😅 its the first website where i use sanity. I‘m migrating a drupal blog to nextJs and Sanity, the blog has about 14k daily active readers
It’s not nothing.
A active visitor reads between 3-5 articles. So it would increment between 40-70k a day
Right. Then I would definitely do it via backporting analytics data with a periodic background job.
ok thanks for your help
Haha. Forgive me for underestimating!
But this is exciting nonetheless. Would love to learn more about how you find that migration and learnings/insights you might have
🙇‍♂️
So far it went pretty good. Only sometimes i had a hard time finding some tutorials about customizing sanity studio or writing custom plugins. i found some usefull videos from kapehe but not too much about customizing other things than fields for documents or so. but i’m still in middle of the process 😅 hopefully everything will fall into place

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?