I'm always excited to take on new projects and collaborate with innovative minds.

Phone Number

+923488488488

Email

abdulsami8777@gmail.com

Social

All posts

Why I Reach for Sanity CMS on Most Client Projects

A working developer's take on Sanity: schemas, GROQ, Portable Text, previews, and the read-token gotcha that silently breaks Next.js fetches.

Over the last few years I have shipped a lot of content-driven sites, and Sanity has become my default headless CMS for most of them. It is not the flashiest choice, but it gets out of the way and lets me model content the way the project actually needs. Here is why I keep reaching for it, and a couple of things that have bitten me along the way.

Content as structured data, not HTML blobs

The thing that sold me on Sanity is that content is real structured data, defined by schemas you write in code. A blog post is not a wall of HTML; it is an object with typed fields, references, and validation rules I control. On a recent project I modeled posts, authors, and case studies as separate document types with references between them, so an author change propagates everywhere instead of being copy-pasted into each post.

Because schemas live in your repo, they go through code review and version control like the rest of the app. When a client asks for a new field, it is a small commit, not a support ticket to some vendor. This also means the content model is documented by definition: anyone reading the schema files knows exactly what shape the data takes.

My one piece of hard-won advice here is to spend real time modeling content before you build anything. It is tempting to dump everything into one giant document, but you pay for that later when you need to reuse a chunk somewhere else. Break things into focused document types and use references early.

GROQ, the Studio, and Portable Text

Querying happens through GROQ, Sanity's query language. It feels strange for about a day and then becomes hard to give up. I can fetch a post, resolve its author reference, count related posts, and shape the exact JSON my front end wants in a single query, with no over-fetching and no separate join logic in my code.

The Studio is the editing interface, and it is just a React app you configure and deploy yourself. That means I can customize inputs, add preview panes, and tailor the editor experience to a non-technical client instead of forcing them into a generic admin panel. Editors get something that matches their actual workflow.

Rich text is stored as Portable Text, which is JSON rather than HTML. This is great for reuse: the same content renders to a web component, a plain-text email, or a native app without re-parsing markup. The tradeoff is that you have to write a serializer to turn Portable Text into your front-end components, including custom blocks like callouts or embedded images. Budget a little time for that mapping on the first project.

Wiring Sanity into a Next.js front end

On the front end I pair Sanity with Next.js and do all my data fetching in server components. Sanity's image pipeline is a quiet favorite of mine: I store one asset and request whatever width, crop, and format I need at render time through the image URL builder, so I am not juggling a folder of resized files. Combined with Next's image handling, this keeps pages light without manual work.

For editorial workflows, Sanity's real-time updates and preview support let clients see draft changes before publishing. Setting up a preview route that pulls drafts with a token, while production pulls only published content, covers the common case of a client wanting to review before going live.

Two gotchas that cost me real time

The first is dataset visibility and read tokens. On my own site I had published posts that simply would not appear, while other document types loaded fine. The dataset was set to public, but anonymous reads were inconsistent for some document types, so the page silently rendered "No posts yet" with no error to chase. The fix was to attach a server-only read token to the client and set useCdn to false when a token is present, since a token and the CDN are mutually exclusive. Because all my fetches run in server components, the token never reaches the browser.

The second is permissions on tokens. An Editor token can write content but may not be allowed to change dataset ACLs, so do not assume the token you seed content with can also flip a dataset to public. Sort out which token does what early, keep the privileged ones server-side, and rotate anything you ever paste into a chat or a shared doc. Get those two things right and Sanity is one of the smoother parts of the stack.

Back to all posts