Incrementing values in Sanity for analytics purposes
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.
Recommended Solutions
1. Use Sanity Functions with Atomic Increments (Recommended)
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:
- Log view events to a separate system or queue
- Use a Sanity Function on a schedule to batch-update view counts periodically
- 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.
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.