
How I implemented Sanity CMS in my Portfolio?
Published: 7/4/2025
Introduction ππ»
Let's be honest, managing web content can be a real drag. Whether you're stuck with the basics like Markdown, dealing with slow full site redeploys, fighting with a traditional CMS, or even encountering the production limitations I faced with Notion's need for constant updates, the process often feels inefficient and pulls you away from more important tasks like coding. That's why I'm excited to share how integrating Sanity CMS with Next.js 15's App Router completely revolutionized my workflow, allowing for instant blog updates without any redeployments. If this sounds like the solution you've been looking for, keep reading.
What is a CMS π€
Content Management System
So, what's the deal with a CMS, this tool I keep mentioning? Think of it like updating a menu in a restaurant. You wouldn't need to redesign the whole restaurant, right? You'd just change the menu. A CMS works similarly for your website's content. Instead of messing with code to add a blog post or swap out an image, it gives you a simple online dashboard β a backend β where you can easily manage everything. It's the smart way to keep your website fresh without needing to be a tech expert or redeploying your entire site for every little update.
For example, when I write a new blog post using the Sanity CMS interface, it gets updated on my website almost instantly. This happens behind the scenes thanks to Sanity's webhooks and Next.js's on-demand ISR (Incremental Static Regeneration), meaning I don't have to manually redeploy my entire site just to share my latest thoughts. It's the smart way to keep your website fresh without needing to be a tech expert or redeploying your entire site for every little update
Why I Use Sanity + Next.js?
Initially, I used Notion as a CMS using react-notion-x
. While it worked in development, it struggled in production β timeouts, slow rendering, and unreliable Incremental Static Regeneration (ISR). Thatβs when I decided to switch to Sanity CMS.
Sanity is a powerful headless CMS β meaning your content is stored separately from your code.
- Next.js 15 provides great support for static generation (SSG), server components, and Incremental Static Regeneration (ISR), which helps with both speed and SEO.
With this combo, I can:
- Easily manage blogs via Sanity Studio
- Automatically update the frontend when new content is published
- Use ISR or on-demand revalidation for better performance
The Architecture βοΈ

Here's a look at how the different parts of my blog integration communicate to enable those instant updates:
- Sanity Studio: This is my custom admin dashboard where I can easily create and manage all my blog content.
- Next.js: My website's frontend, which fetches the blog data using GROQ queries (a special way to ask Sanity for your blog data) via the
next-sanity
library. It then renders the list of blog posts and the individual blog pages. - ISR + Webhook: The foundation for automatic updates. My blog pages are initially generated when the site is built for speed.
- When a blog is added/updated in Sanity: Sanity automatically sends a webhook (an automated message) to a specific address on my website (
/api/revalidate
). - That webhook call triggers
revalidatePath()
: This tells Next.js to update only the specific blog post or the blog listing page that has changed. - Changes go live without a full site rebuild: This entire process ensures that your latest content is always visible without any manual deployment needed, making content updates incredibly efficient.
Under the Hood: Exploring the Implementation π€
Blog Listing Page: Fetching All Posts
On my main /blogs page, I need to fetch all the published blog posts to display a list. Here's the GROQ query I use to achieve that:
*[_type == "post"] | order(publishedAt desc){
_id,
title,
slug,
publishedAt,
excerpt,
image
}
Dynamic Blog Pages: Displaying Individual Posts
Now that we've seen the GROQ query that fetches our blog post data, let's take a look at how I use that data within my Next.js application to display the list of blog posts on the /blogs
page:
import { getClient } from '@/lib/sanity/client';
import { SanityDocument } from 'next-sanity';
import Link from 'next/link';
import { BlurFade } from '../components/magicui/blur-fade';
const client = getClient();
const query = `*[_type == "post"] | order(publishedAt desc){
_id,
title,
slug,
publishedAt,
excerpt,
image
}`;
const blogs: SanityDocument[] = await client.fetch(query);
export default function BlogListPage() {
return (
<main className="max-w-3xl mx-auto mt-8 space-y-8 p-4 sm:p-6">
{blogs.map((blog, index) => (
<BlurFade key={blog._id} delay={0.3 + index * 0.1}>
<Link
href={`/blogs/${blog.slug.current}`}
className="group block border border-neutral-200 dark:border-neutral-700 rounded-xl p-5 transition-transform duration-200 hover:scale-[1.02] hover:border-black/10 dark:hover:border-white/20 hover:shadow-md dark:hover:shadow-slate-800/30 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<h2 className="text-lg sm:text-xl font-semibold group-hover:underline underline-offset-4 decoration-neutral-400 dark:decoration-neutral-600">
{blog.title}
</h2>
<p className="text-xs sm:text-sm text-gray-500 dark:text-gray-400 mt-1">
{new Date(blog.publishedAt).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
})}
</p>
{blog.excerpt && (
<p className="mt-2 text-sm text-gray-700 dark:text-gray-300 line-clamp-3">
{blog.excerpt}
</p>
)}
</Link>
</BlurFade>
))}
</main>
);
}
This is the code I use on my website to actually show the list of blog posts you see on the /blogs
page. First, it grabs the blog post data from Sanity CMS using the query we just looked at. Then, for each blog post it finds, it displays the title, a short preview (the excerpt), and the date it was published. Each blog post title is also a link, so you can click to read the full article.
How Revalidation Works: The Magic Behind Instant Updates π
The key to updating content without redeploying lies in Next.js's Incremental Static Regeneration (ISR) and a webhook from Sanity. Here's the Next.js API route (/api/revalidate/route.ts) that handles the revalidation when triggered:
import { NextRequest } from 'next/server'
import { revalidatePath } from 'next/cache'
export async function GET(req: NextRequest) {
const secret = req.nextUrl.searchParams.get('secret')
const path = req.nextUrl.searchParams.get('path')
if (secret !== process.env.REVALIDATE_SECRET) {
return new Response('Invalid token', { status: 401 })
}
if (!path) {
return new Response('Missing path param', { status: 400 })
}
try {
revalidatePath(path)
return new Response(`Revalidated path: ${path}`, { status: 200 })
} catch (err) {
return new Response('Error revalidating : '+err, { status: 500 } )
}
}
This route first checks for a secret token to ensure the request is authorized. Then, it retrieves the path parameter, which specifies which part of the website needs to be updated. Finally, revalidatePath(path) tells Next.js to regenerate that specific page or route in the background.
Triggering Revalidation: Sanity Webhooks in Action
Sanity automatically sends a webhook to my portfolio whenever I publish or update a blog post. This webhook calls the /api/revalidate endpoint we just looked at. Here are example curl commands that simulate what Sanity sends:
curl "https://ashishgogula/api/revalidate?secret=***&path=/blogs"
curl "https://ashishgogula/api/revalidate?secret=***&path=/blogs/cms"
Sanity allows you to configure webhooks in its settings. When a blog post is published or updated, it sends a GET request to the specified URL with a secret token. This triggers the revalidation process, ensuring your website reflects the latest changes without a full rebuild.
Result: A Fast, Scalable Blog System
- I write/update blogs from Sanity Studio
- Webhook revalidates the updated page
- Next.js serves the latest content instantly
Moving from Notion to Sanity was a game-changer. My site is now fast, reliable, and easy to update β the way modern content platforms should be.
-----------------------------------------------------------------------------
Want to dive deeper into Sanity and learn how to implement it in your own applications? Find more information on the official Sanity documentation:
https://www.sanity.io/docs/getting-started
-----------------------------------------------------------------------------
The Real Work: Redesign, Customization, and Lots of Commits π
It might seem like a straightforward integration on paper, but the reality is, this "basic implementation overview" only scratches the surface of the work involved. I spent a significant amount of time not just connecting the pieces, but truly rethinking my content architecture to best leverage Sanity's flexible data modeling. Setting up Sanity from scratch, understanding its core concepts, and deploying it effectively took a dedicated effort. And what you see in the final UI of this blog is the result of considerable customization β over 30 commits in GitHub were dedicated to tweaking the design and functionality to perfectly fit my needs and create the streamlined content management experience I envisioned. It was a journey filled with design decisions, trial and error, and a fair amount of problem-solving. If you find yourself embarking on a similar integration, feel free to reach out β I'd be happy to share any insights I gained along the way!