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
- 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.
- You get a file like
s6kuy1ts-2026-08-12-2026-08-19.ndjson.gz. Click it to unzip. - Run the analyzer against the unzipped file:
- 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:
Loading video player…
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:
| Traffic | Bandwidth | Share of bytes | Requests |
|---|---|---|---|
| Images | 1.5 GB | 71% | 35,639 |
| Files (mostly video) | 555.5 MB | 26% | 345 |
| GROQ query responses | 42.4 MB | 2% | 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:
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
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:
| Query | Bandwidth | Requests |
|---|---|---|
Settings | 8.7 MB | 4,226 |
Footer | 4.7 MB | 1,642 |
Navbar | 2.9 MB | 1,651 |
Page | 1.6 MB | 684 |
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.
| File | Size | Downloads | Total bandwidth |
|---|---|---|---|
0ba1d201.webm | 2.6 MB | 3 full, 241 partial | 408.8 MB (19.2%) |
b9ba5686.webm | 2 MB | 2 full, 53 partial | 97.9 MB (4.6%) |
198c59de.webm | 1.3 MB | 2 full, 13 partial | 18 MB (0.8%) |
61a69683.webm | 1.2 MB | 3 full, 12 partial | 13.6 MB (0.6%) |
ef46f076.mp4 | 2.1 MB | 2 full, 7 partial | 12.4 MB (0.6%) |
3a642359.mp4 | 2.4 MB | 2 full, 2 partial | 4.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:
| Warning | What the report showed |
|---|---|
| Unpublished drafts in public traffic | 21.7% of content requests (3,032 requests, 13.4 MB) |
| API version drift | 33 different API versions requested, 166 requests with none set |
| Invalid requests | 3.3% of all traffic: 879 bad requests, 1,101 not-founds |
| Test setups using live content | 1.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
- Add
width()andauto("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. - Cache the navigation queries and revalidate on publish. 16.3 MB and 7,519 requests for content that changed maybe twice that week.
- Upgrade next-sanity to v13 if you're on Next.js 16. This is the difference between 2 requests per link hover and 7.
- Move video to HLS. That 408.8 MB webm shrinks to a few MB, because viewers only download the seconds they watch.
- 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.
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.








