Skip to content




Sanity Log Analyzer: see where you're blowing your usage

Sanity Log Analyzer: see where you're blowing your usage

We built a free CLI that turns Sanity's request log export into a report of where your bandwidth goes. Here's what it found on our own starter.


Click me for the TL;DR (too long; didn't read)

Sanity Log Analyzer is a free CLI (npx sanity-logs analyze) that parses your project's request log export on your machine and shows where the bandwidth goes. One week of our own starter: 59,508 requests, 2.1 GB, one jpg responsible for 329.9 MB, and the navigation queries costing 10x more than the page content.

We built Sanity Log Analyzer (opens in new tab) to catch Sanity overages at a more granular level than the usage dashboard shows. It's the same tool we use on our own client projects, and our dev team maintains it.

You can test things such as which GROQ queries cost the most, which images shipped the most bytes, who was fetching, what failed, and how long everything took.

The tool is open source. You download your request logs from your organization settings, run one command, and the report opens in your browser.

How to run it

  1. Open your project on sanity.io and go to Usage, then Request logs. Click Download. The export covers the last 7 days (not including today), caps at 1 GB, and you can generate a fresh one every 24 hours.
  2. You get a file like s6kuy1ts-2026-08-12-2026-08-19.ndjson.gz. Click it to unzip.
  3. Run the analyzer against the unzipped file:
bash
npx sanity-logs analyze s6kuy1ts-2026-08-12-2026-08-19.ndjson
  1. The dashboard opens in your browser with the full report.

The CLI doesn't need an account, a config file, or an API token.

We also recorded a walkthrough of the tool, if you'd rather watch than read:

What it found on our own starter

The report below is for turbo-start-sanity, our own template, from August 24 to 30. The main thing to look at: 59,508 requests, 2.1 GB of bandwidth, a 3.3% error rate, and a p50 response time of 27ms.

The findings are interesting because we maintain the template regularly and use it as the foundation for all our Sanity and Next.js projects. It still tripped five of the analyzer's warnings. This is a good case of why Sanity Log Analyzer can be useful. It gets into granular details and highlights what's costing usage, even on a project you'd assume was already clean.

Here's where the 2.1 GB went:

TrafficBandwidthShare of bytesRequests
Images1.5 GB71%35,639
Files (mostly video)555.5 MB26%345
GROQ query responses42.4 MB2%13,986

The remaining 9,500 or so requests, streaming checks, studio traffic, and errors, barely register in bytes.

One image cost 329.9 MB

The single costliest asset in the whole project was a 2560x1440 jpg. It was requested 1,013 times and shipped 329.9 MB, about 326 kB per request, because every request fetched it without a width parameter and without auto=format. I've worked on fashion sites before and understand why it's important to have high-quality images, but this is ridiculous. Nobody needs the full-size original on every request.

Another 18,682 requests (249.8 MB) fetched originals at full size with no width set, and nearly every image in the top 50 carried a "No auto-format" flag in the report. Luckily, the fix is one line wherever you build image URLs, see below:

ts
// before: ships the original, 2560px and all
urlFor(image).url()

// after: sized for the layout, re-encoded as webp or avif
urlFor(image).width(800).auto("format").url()

auto=format alone typically cuts 30 to 70% off jpg and png weight, and the width parameter stops a phone from downloading a 4K original for a 400px slot.

I don't want to rant about this too much, but image optimization is super important for site performance, and the analytics depend heavily on those factors too. So we need to get our act in gear for our own template, and you should take notes too.

No spam, only good stuff

Get posts like this in your inbox

Only god knows why anybody would purposefully subscribe themselves to a newsletter that moans about development. These poor souls did though
Profile 1
Profile 2
Profile 3
Profile 4
Profile 5

The navigation cost 10x more than the pages

If there's one thing I've broken the most on Roboto's site, and that has cost us the most in Sanity bandwidth, it's the nav.

In our case, the turbo-start-sanity log report showed Settings was the most expensive GROQ query, with Footer and Navbar right behind it. Can't say I was surprised:

QueryBandwidthRequests
Settings8.7 MB4,226
Footer4.7 MB1,642
Navbar2.9 MB1,651
Page1.6 MB684

This pattern comes up on client projects all the time. Someone wants nav edits to show up on the live site instantly, not after the next build. So the navbar, footer, and settings get fetched fresh on every page view. Fair enough, but now you're paying on every request for content that changes a few times a month.

The fix is to do caching with tags. This way, you fetch the navigation once, tag it, and revalidate the tag when someone publishes. The editor still sees changes within seconds, and the query count drops from thousands per week to dozens. Clever or what?

Half the content traffic was asking whether anything changed

The log analyzer report showed that 49.3% of the project's content requests were freshness checks, 6,893 requests spending 18.3 MB just asking "anything new?" Also, it flagged 4,217 streaming connections that kept dropping and reconnecting, with a median lifetime of 339ms. Bear in mind, these connections are meant to stay open for minutes. Ours were reconnecting in under half a second.

If your numbers look like this, there's a good chance you're on Next.js 16 with next-sanity v12. This is a known bug. Next.js 16 changed how <Link> prefetching works, and SanityLive clears the client-side cache on every live event, which re-triggers all those prefetches and their ISR writes. You can read the details here (opens in new tab). We've also updated our own writeup on everything Sanity, GROQ, and Next.js. Read it here.

You can fix this simply by upgrading to next-sanity v13. We know because we fell for this one ourselves. Thankfully, the log report saved us from a lot of headache down the line.

A 2.6 MB video turned into 408.8 MB

Unoptimized media files have historically caused bandwidth problems for us. So what the report highlighted wasn't news, but it was useful to see which files were most problematic. One file in particular, a 2.6 MB webm, was downloaded in full 3 times and partially 241 times, for a total of 408.8 MB, 19.2% of everything the project served that week.

FileSizeDownloadsTotal bandwidth
0ba1d201.webm2.6 MB3 full, 241 partial408.8 MB (19.2%)
b9ba5686.webm2 MB2 full, 53 partial97.9 MB (4.6%)
198c59de.webm1.3 MB2 full, 13 partial18 MB (0.8%)
61a69683.webm1.2 MB3 full, 12 partial13.6 MB (0.6%)
ef46f076.mp42.1 MB2 full, 7 partial12.4 MB (0.6%)
3a642359.mp42.4 MB2 full, 2 partial4.8 MB (0.2%)

The reason being, 555.5 MB of video was served as progressive download rather than HLS, meaning browsers fetch the whole file (or big chunks of it) before and during playback, on repeat, with no adaptive quality.

Video hosted as a plain Sanity file asset will always behave like this. Generally speaking, anything other than a short hero loop should be streamed for this exact reason. We use Mux for this on every project, where the same video becomes HLS segments and viewers only download what they watch. Simplifies the whole thing.

The small warnings

The rest of the report's warnings:

WarningWhat the report showed
Unpublished drafts in public traffic21.7% of content requests (3,032 requests, 13.4 MB)
API version drift33 different API versions requested, 166 requests with none set
Invalid requests3.3% of all traffic: 879 bad requests, 1,101 not-founds
Test setups using live content1.4 GB across 29,749 requests from non-production environments

Unpublished draft request is an interesting one. The template has around 8,500 requests a day, and a good chunk of this is not even real visitors. From that perspective, one in five content requests asking for unpublished drafts is high. These numbers would make sense for a large team editing all at once. But if the draft-to-traffic ratio seems off, there's a good chance the draft perspective is set wrong and leaking into production traffic.

The rest of the findings are quick fixes. For example, you can set one apiVersion per project and use it everywhere, so a query that works locally doesn't 400 in production. Invalid requests still count as traffic. Lastly, the top referrer in the whole report was http://127.0.0.1:4173, a local dev server that pulled 797.9 MB from the production dataset in a week. Dev traffic counts toward usage like any other. This simply means one of our devs went on one.

What we'd fix first, in order

  1. Add width() and auto("format") to every image URL. It's the biggest pool of bytes (1.5 GB), the simplest change, and it deletes the 249.8 MB of full-size originals outright.
  2. Cache the navigation queries and revalidate on publish. 16.3 MB and 7,519 requests for content that changed maybe twice that week.
  3. Upgrade next-sanity to v13 if you're on Next.js 16. This is the difference between 2 requests per link hover and 7.
  4. Move video to HLS. That 408.8 MB webm shrinks to a few MB, because viewers only download the seconds they watch.
  5. Point dev and preview environments at a non-production dataset, pin one API version, and keep draft perspectives out of public traffic.

When you don't need this

If your usage page shows a few thousand requests a week and you're comfortably inside your plan's bandwidth allowance, skip the audit altogether. We created this tool for heavy-traffic websites with a lot of moving parts on the editorial side. These teams can benefit from going into the details, optimizing, and saving money where possible.

For everyone else, Sanity's pricing tiers decide what an overage costs.

Services
$ Want us to read your logs?
We run this analysis at the start of every Sanity performance audit, then fix what it finds. Bring us a week of logs and we'll bring the report.
>Talk to us about a Sanity audit

Oh, forgot to mention, you can only generate one log export every 24 hours, so let it cool down before trying to sneak a quick one in before your next meeting. Thanks for reading, and tell us if you tried the tool and found it useful. We'd love to hear from you.

Frequently asked questions

How do I download my Sanity request logs?
Open your project on sanity.io, go to Usage, then Request logs, and click Download. The export covers the last 7 days (not including today), caps at 1 GB, and you can generate one new export every 24 hours. It arrives as a gzipped NDJSON file like s6kuy1ts-2026-08-12-2026-08-19.ndjson.gz.
Is Sanity Log Analyzer free, and does it upload my logs?
Yes, it's free, and nothing is uploaded. You run npx sanity-logs analyze against the .ndjson file and it parses everything on your machine, then serves the report from a local URL. The only external requests the report makes are image previews, which load from your own Sanity CDN.
What counts toward Sanity bandwidth?
Every byte Sanity's APIs and CDN serve: image transforms, file downloads, GROQ query responses, and streaming connections. In our sample week the split was images 1.5 GB (71%), files 555.5 MB (26%), and GROQ query responses 42.4 MB (2%). Images and video dominate on almost every content site.
Why is my Sanity bandwidth so high?
The usual cause is image URLs without a width parameter or auto=format, so the CDN serves the original upload at full size. In our own report, 18,682 requests (249.8 MB) fetched originals with no width set, and the single costliest asset was a 2560x1440 jpg that shipped 329.9 MB across 1,013 requests.
Does the Sanity studio add much to my bill?
The studio adds less than most people expect. In our sample week it accounted for 9.9% of requests, while images and video files accounted for 97% of the bytes. Editors clicking around the studio are almost never the reason a bandwidth bill goes up.
What does auto=format do on Sanity image URLs?
It lets the Sanity CDN re-encode the image into whatever modern format the browser supports, usually webp or avif, which is typically 30 to 70% smaller than the original jpg or png. Combined with a width parameter it's the single biggest bandwidth saving available. Every top image in our report was missing it.
Why did my Sanity API requests spike after upgrading to Next.js 16?
Next.js 16 changed Link prefetching, and next-sanity v12's SanityLive clears the client cache on every live event, which re-triggers those prefetches. Sanity documented reports of 4x request load, with worst cases at 7 to 10x. The fix is upgrading to next-sanity v13, released May 2026, which changes the default cache invalidation.
Should navigation content be fetched on every page view?
No. Navbar, footer, and site settings change a few times a month, so cache them with a tag and revalidate on publish. Our starter fetched them per page view instead, and those three query groups cost 16.3 MB across 7,519 requests in one week, ten times the bytes of the actual page content.

About the authors

Sne Tripathi
Sne Tripathi

Account Executive

Account Executive at Roboto Studio, bridging the gap between client needs and technical solutions. Ensures every project delivers real business value.

Hrithik Prasad

Tech Lead with expertise in React, Next.js, and Sanity CMS. Loves building performant web applications and sharing knowledge through technical content.

Jono Alford

Founder of Roboto Studio, specializing in headless CMS implementations with Sanity and Next.js. A Sanity Pioneer and first-cohort Sanity Community Ambassador, focused on editorial experiences that help teams ship faster.

Chibuike Maduabuchi

Design Engineer building digital products with a focus on structure, usability, and craft. Works across product thinking, interface design, and frontend engineering for complex workflows and data-intensive systems.



Related posts





Get in touch

Tell us what you're building. We reply within one working day. Jono or someone on the team picks up every message personally.

By sending this you agree to our privacy policy. We only use your details to reply.