Easing into the App Router with the Sanity Toolkit for Next.js
Written by Marissa Ghassemian, Knut Melvær
Picture this: you're on the hunt for information or looking to make a purchase online, but the webpage you clicked is taking an eternity to load. Frustrating, right? How often do you just bail when this happens? We all know these experiences drive users away, but let’s face it: it takes a lot of time and skill to develop a performant site, especially for the dynamic, rich experiences that our visitors love. It’s also worth it: websites that meet user expectations and needs instill a sense of trust and reliability in a brand, make it easier for users to achieve their online objectives, and ultimately help you grow your business.
Today we’re happy to share our latest improvements to Sanity’s Next.js toolkit, which combines the might of our client library,@sanity/client, and Next.js, all in one convenient package. Together with Next.js App Router, these improvements reduce the burden on developers to build the complex caching infrastructures needed to power fast, fresh content loads while letting you remove lines of code, perform less maintenance and make fewer API calls.
More delightful = more demanding
Brands that win are constantly launching richer, more dynamic experiences that stand out. The teams that make these experiences a reality are faced with major challenges as they need to maintain performance as sites become more:
- Asset heavy. Videos and images are key to delivering engaging, memorable content— 91% of consumers reported preferring visual content to written content. They’re also a huge contributor to page weight and often the main culprit in slower load times.
- Personalized. Personalization requires dynamic builds with fresh content to deliver tailored experiences. And it’s worth the effort: BusinessWire reported 80% of consumers are more likely to shop with brands that show they know them.
- Updated frequently. Most brands have pockets of content that are refreshed often. Within certain use cases, however, content updates are near-constant. Think about Media companies needing to tenaciously display the latest news.
Overcoming performance drains with Sanity and Vercel
Staying ahead of your competitors requires harnessing the power of innovative tools and platforms. Vercel, a cloud platform for building, deploying, and scaling apps and websites, has been leading the charge in helping development teams meet the ever-increasing demands of their sites. They’re also the creator and maintainer of Next.js, one of the most popular web frameworks on the planet right now for React. Sanity, the Composable Content Cloud, offers the most control and customization of any CMS. Together, the Next.js, Vercel, and Sanity stack is unstoppable.
Sanity has improved how we build with content for our clients. We can give them bespoke content workspaces that make sense for their business, while enjoying the great developer experience that comes with Sanity Content Lake. With the new App Router, we have even more flexibility building successful user experiences, while ensuring great performance. Content creators can push content to production without having to wait for their changes to appear on the site, knowing that the site stays performant for their audience. It seems like this should be table stakes, but it can be really hard to do without the abstractions given to us by Sanity and Next.js.
Grant Sander, VP of Engineering, Formidable
From static to dynamic
To address the need for a more scalable way to update content, Next.js released Incremental Static Regeneration (ISR) in 2021. With ISR, developers can use static-generation on a per-page basis (rather than having to rebuild an entire site for an isolated change). This has been a huge value for the developer community, but comes with challenges:
- Keeping track of what URLs need to be invalidated. In most real-world scenarios where content is reused across pages, you need to keep track of every page URL affected by a content update so you know which pages to send an invalidation request for. This type of caching architecture requires a fair amount of manual work to be done.
- Reloading the entire page. Reloading the entire page after an invalidation request is often a big hit to your performance when you have the type of heavy, rapidly evolving content we all love. This means slower load times, increased bandwidth consumption, and degraded user experiences.
Now: App Router with React Server Components
Fast forward to 2023, Vercel’s latest release of Next.js 13 introduced App Router, giving you a deeper level of control to create sites that perform as fast as static sites, but are completely dynamic. How? By defining your caching strategy—dynamic vs. static—for individual components within a page, you avoid entire page reloads. This is powered by React Server Components, which makes every component on your website responsible for its own data. Using these new data fetching and caching paradigms, revalidation now moves to individual fetching.
The biggest mental model shift in Next.js 13 is to stop thinking which pages should be static or dynamic and start thinking about what data is static or dynamic. Moving the static and dynamic decisions to data fetching gives you more control over when and where to serve static or dynamic content.
Vercel
How exactly do you indicate what components need to be updated without revalidating the entire page? You can use revalidation tags. Revalidation tags mark a specific request correlated to the specified tag to be expired when information changes. This type of technology gives you the customizability to invalidate content with the granularity you see fit, for example, only this author, all authors, and so on. – the power is in your hands.
Sanity for the assist: Built-in compatibility for Next.js
Because Sanity treats content as data (structured and queryable) and offers a high degree of query flexibility with GROQ, it’s a well-suited headless CMS to pair with App Router. And we've fine-tuned our Next.js toolkit, next-sanity, to give developers a more intuitive and efficient experience as they build high-performance, user-delighting sites.
More for developers to love
Next.js is all about giving developers tooling to build as quickly as possible with minimal configurations. With the same intent, we’ve combined the power of our client library, @sanity/client, and Next.js, in one convenient package so developers can enjoy the DX of next-sanity and Next.js in one place. Benefits include:
- Seamless integration: Native integration means that @sanity/client is purpose-built to work with Next.js. This eliminates compatibility issues so developers don’t need to invest time in custom integrations and can reduce risk of bugs and errors.
- Faster development: Developers can save valuable time by leveraging the built-in functionality. They can quickly start building sites without having to write extensive custom code or configurations that accelerate development cycles.
- Consistency and best practices: Native integration follows Next.js best practices and coding conventions. This ensures that developers adhere to established standards, leading to more maintainable code.
- Performance optimization: With core capabilities integrated into @sanity/client, the library is fine-tuned for performance within the Next.js ecosystem. This means that developers can rely on efficient and well-optimized code, resulting in faster-loading websites.
- Reduced learning curve: Developers familiar with Next.js can easily transition to using @sanity/client because it aligns with the framework they already know, so they can be productive from the start.
- Community support: You can find documentation, examples, and community-driven solutions readily available in the Sanity Community, making it easier to troubleshoot issues, seek assistance and get inspiration.
- Future updates and compatibility: As Next.js evolves, Sanity will stay up-to-date with changes so developers can take advantage of the latest and greatest, such as the aforementioned App Router and Revalidation Tags, and maintain compatibility as the framework evolves.
Easing into App Router
React Server Components and the App Router represented a fundamental shift in how you build web applications with Next.js, and it can be daunting to have to re-learn a framework. Fortunately, Next.js lets you adopt the App Router incrementally alongside the Page Router. What’s more, our next-sanity toolkit is compatible with both! This means you get advanced caching and tag-based revalidation features to make your life a lot easier. In other words, using Next.js and Sanity together is a good way to get started with React Server Components, and start reaping its benefits.
Making Fetch happen
Fetch functionality in Next.js refers to the ability to retrieve data from an external source or API in a website. You can fetch data either at build time on the server or during runtime on the client, boosting not only your site's speed but also its SEO and overall user experience. In simple terms, it means quicker page loads, better user experience, better SEO rankings, less work for your client-side code, and even the ability to pre-render pages with ever-changing data. All leading to a smoother, faster, and more efficient website.
Our next-sanity toolkit is now fully compatible with Next.js’ capabilities, bringing together the best of its internal data-fetching methods and our own client library. This combo significantly reduces the number of API calls needed to keep content updated. Instead of constantly polling for changes, you can set up a webhook to trigger the appropriate calls only when a change occurs, eliminating the need for expiration checks. This makes it especially effective for e-commerce and media sites that require real-time updates and robust caching strategies.
While navigating caching options can often be complex and time-consuming, Sanity lets you easily opt for the most fitting caching approach for any page or component. And if your project demands more nuanced control—common in mature, dynamic sites—you can use revalidation tags for fine-grained management, making it easier to keep your content both fresh and efficient.
Example of a component with data fetching + revalidation tag
// app/components/TestimonialCard.tsx
async function FeaturedTestimonialCard() {
const data = await sanityFetch({
query: '*[_type == "testimonial" && featured == true][0]{name, title, photo}',
// Will automatically trigger for when a webhook contains the `testimonial` type
tags: ['testimonial'],
)}
return (
<Card>
<Image {...data.photo} />
<Byline>{data.name}{title ? ` (${title})` : ''}</Byline>
</Card>)
}
GROQ-Powered Webhooks + App Router
Revalidation tags and GROQ-Powered Webhooks make it much easier to hit the sweet spot between reducing API calls to Content Lake and never seeing stale content on your site. GROQ-powered Webhooks let you set up notifications to trigger (almost) any content change and let you freely build a payload based on that content – making it the most advanced content webhook feature in the industry. Companies that need content changes to publish quickly, like news and media, use GROQ-Powered Webhooks to monitor and respond to create, edit, and delete events.
To trigger an invalidation request, the URL/path-based approach has worked fine for simple content types like articles and posts. It has been harder to implement revalidation for more complex cases that are typical for modern web experiences. Let‘s say you have a document type for testimonials, that you use for different parts of your website. Before you had to figure out all the URLs that use the testimonial type to revalidate the cache for where it’s used. Using the tags-based approach, when you query Content Lake you can add a tag that says testimonial
to the same function that fetches the data. Doing this allows Next.js to keep track of the data-fetching URLs that have asked for data and have the associated testimonial
tag.
When a content change triggers a webhook, you can include data that tells Next.js which tag to revalidate. A simple example is to include the document _type
in the webhook's payload, and use the _type
name as the tag. In our example, the document type is testimonial
, and when the webhook hits Next.js' serverless function, it matches that to the revalidate tags. Without the need to keep track of what URLs are impacted your code becomes significantly simpler to manage, compose, and comprehend.
API route for handling incoming GROQ-powered Webhooks that has {_type}
as their body:
// app/api/revalidate/route.ts
/*
The relatively simple API route that handles the incoming request from a GROQ-Powered Webhook.
The webhook here could have the following settings:
filter: `_type in ["testimonial", "page"]`
projection: `{_type}`
trigger on: [x] create [x] update [x] delete
*/
import {revalidateTag} from 'next/cache'
import {type NextRequest, NextResponse} from 'next/server'
import {parseBody} from 'next-sanity/webhook'
export async function POST(req: NextRequest) {
try {
const {isValidSignature, body} = await parseBody<{_type}>(
req,
process.env.SANITY_REVALIDATE_SECRET,
)
if (!isValidSignature) {
const message = 'Invalid signature'
return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
}
if (!body?._type) {
const message = 'Bad Request'
return new Response({message, body}, {status: 400})
}
// If the `_type` is `testimonial`, then all `client.fetch` calls with
// `{next: {tags: ['testimonial']}}` will be revalidated
await revalidateTag(body._type)
return NextResponse.json({body})
} catch (err) {
console.error(err)
return new Response(err.message, {status: 500})
}
}
Structured Content + App Router
Structured content is already a great way to update your content in a single place and have it be automatically updated everywhere it’s referenced without keeping track of URLs. Combining that with the power of Next.js’ cache and Vercel’s data layer will make the propagation of your content updates faster than ever – especially when combined with revalidation tags. Not only does this combining force help with the speed of updates, but it also comes at a much lower cost as structured content lets you know what parts of the cache don’t need to be revalidated. This means the longer the content lives in the cache the less API calls you need to make.
This is typically a difficult trade-off that an organization has to make – do they want to pay the price of high API usage rates to keep content fresh with changes going out quickly or do they want to keep costs low but have stale content on their site with updates being slowly rolled out. Revalidation tags are a great mechanism to get the best of the both worlds: maximum content reuse and content updates propagating without a delay.
Using App Router Effectively
App Router is key for any site built with Next.js and Sanity. However it’s particularly useful for:
- Slow-to-rebuild content-heavy or dynamic pages including:
- Product landing pages
- Product display pages
- Content that is referenced in several locations throughout a site such as:
- Header and footer content
- Call to Action modules
- Contact information
- Navigation
- Timely content releases, covering:
- Critical news updates
- Limited product offers
- Campaigns
Cutting edge capabilities for quick content updates
Using Vercel’s edge caching technology with Sanity’s unmatched content management capabilities users can redefine what it means to serve dynamic and static content. Combining the customization capabilities of structured content, GROQ-Powered webhooks, App Router, and revalidation tags your content is sure to deliver at the frequency specified by you. No matter how small or large the content update is, you’ll be sure to never show stale content again.
Explore the power of Sanity's Next.js toolkit and elevate your web development today!
Install Next-Sanity