You're building a content-heavy Next.js app with Sanity CMS and want a scalable architecture that grows with your project. Your components need different pieces of data, so naturally you write clean, modular code where each component declares what it needs.
This works beautifully. But as your application scales, you start thinking about optimization:
- How can you minimize API calls during ISR regeneration for faster builds?
- What's the best way to avoid waterfall requests that could slow down content delivery?
- How do you structure data fetching to handle high-traffic scenarios efficiently?
You might be considering different approaches:
Centralizing all data fetching
in page components, but this creates tight coupling between pages and components
Using multiple targeted queries
per component, which is clean but can create sequential request chains
A scalable approach that handles this well: GraphQL Fragment Colocation.
This approach lets you maintain component-level data declarations while composing them into a single, efficient API request - giving you both developer experience and production performance.
By the end of this guide, you'll have a Next.js application that:
Fetches all page data in a single API request
to Sanity CMS
Maintains component-level data colocation
so each component declares exactly what it needs
Provides full TypeScript safety
with compile-time validation
Handles ISR reliably
without rate limiting issues
Scales efficiently
as your content and component tree grows
This approach addresses the tension between developer experience and performance that most approaches require you to navigate.
You'll need:
and
npm/yarn/pnpmA
with GraphQL API enabled
- Basic familiarity with Next.js App Router and React Server Components
Want to skip the setup? Clone the complete template repository and follow its README to get started immediately. Using a coding agent? The CLAUDE.md helps your agent to get started.
If you don't have a Sanity project yet, the Sanity documentation has a great quickstart guide.
Here's what we're going to do:
Understand the core concept
: Exploring the opportunities with fragment colocation
Set up the technical foundation
: Next.js 15, gql.tada, URQL, and Sanity
Build real components
: PostHeader, and Author with fragment patterns
Make ISR bulletproof
: Single-request architecture for reliable background updates
Handle production concerns
: Rate limiting, error handling, and performance optimization
Let's start with the fundamental problem. In a typical Next.js app with a headless CMS, you face this choice:
Option A: Component-Level Data Fetching
This looks clean and modular. Each component owns its data requirements. But in production, this creates:
Separate API calls
to Sanity for a single page
Waterfall requests
during ISR regeneration
Slower page regeneration
due to sequential API calls
Failed background updates
when any single request times out
Option B: Centralized Data Fetching
This is more efficient (2 parallel requests instead of waterfall), but now:
Components are tightly coupled
to the page's data structure
Adding new data requirements
means updating multiple files
Refactoring components
becomes a nightmare
TypeScript safety
is harder to maintain
GraphQL fragments let you have both: component-level data declarations that compose into a single query.
Here's the same example with fragment colocation:
What this demonstrates:
Each component defines a fragment
describing exactly what data it needs
Fragments compose
into the parent query
One API request
fetches all the data for the entire page
TypeScript knows
exactly what data each component receives
Components stay modular
- they only access their fragment's data
The goal is one roundtrip: every page makes exactly one request to your CMS, no matter how many components need data.
Fragments Prevent Over-fetching
Unlike traditional approaches where you might fetch entire objects and only use a few fields, fragments ensure each component declares exactly what it needs. Your IDE will warn you when fragment fields go unused, making over-fetching immediately visible. This component-driven approach means data requirements stay in sync with actual usage.
High-Traffic Considerations
If you're running a high-traffic site (70,000+ pages) that needs to revalidate all content simultaneously (like when updating global components), you might also hit rate limits with multiple API calls per page. The fragment approach reduces multiple API calls to your CMS per page down to 1, which can be the difference between successful and failed regenerations at scale.
In Next.js with ISR, this architecture is important:
Without Fragment Colocation:
- Page regeneration makes multiple API calls
- Any failed request breaks the entire update
- Slower regeneration due to waterfall requests
- Background updates become unreliable
With Fragment Colocation:
- Page regeneration makes one API call
- Single point of failure is easier to handle
- Predictable API usage and faster regeneration
- Background updates succeed consistently
* * *
Let's build this step by step. We'll create a Next.js app that uses fragment colocation with Sanity CMS.
Start with a fresh Next.js 15 project:
Based on your project setup, install the core GraphQL dependencies:
Here's what each package does:
gql.tada: Type-safe GraphQL documents with fragment composition
@urql/next: GraphQL client with React Server Component support
urql: Core GraphQL client functionality
@portabletext/react: Render Sanity's Portable Text content
Your project also includes these additional tools:
Biome
for linting and formatting (instead of ESLint/Prettier)
Tailwind CSS v4
for styling
Next.js 15.5.0
with Turbopack enabled
Add these scripts to your package.json for easier development:
Create your Sanity configuration:
Add your environment variables:
gql.tada needs your GraphQL schema to provide type safety. Your project includes a custom schema generation script:
The generated schema file enables gql.tada to provide full TypeScript safety for your GraphQL operations.
Create a single file that handles both GraphQL configuration and client setup:
Template Repository Available
All the code from this guide is available in the template repository. You can clone it and follow along, or use it as a reference while building your own implementation.
What we built:
Single-file GraphQL setup
combining gql.tada configuration and URQL client
Type-safe GraphQL operations
with your Sanity schema introspection
React Server Component support
with proper URQL integration
Environment-based configuration
with clean error handling
Direct client access
without wrapper functions for better performance
The foundation is ready. Now let's build some components that use fragment colocation.
* * *
Let's create a realistic example: a single blog page with header, content, and author. Each component will define its data requirements as fragments.
First, let's assume you have these document types in Sanity:
After updating your schema, deploy the schema to Sanity:
After deploying your schema, regenerate the GraphQL introspection:
Create a head component with its fragment:
Key patterns here:
Fragment definition
:
postHeaderFragmentdeclares exactly what data this component needs
Type safety
:
FragmentOf<typeof postHeaderFragment>ensures the component only receives the data it declared
Fragment masking
:
readFragment()unwraps the data, preventing access to fields not in the fragment
Component isolation
: This component has no knowledge of how or where its data comes from
Now all these fragments compose into a single page query:
Notice the fragment composition:
GET_POST_BY_SLUGspreads the
authorFragmentusing
...Authorand
postHeaderFragmentusing
...PostHeaderThe fragments array
[authorFragment, postHeaderFragment]tells gql.tada about the dependency
- TypeScript knows exactly what data each component can access
What this demonstrates:
Two components
each declared their data needs as fragments
One page query
composed all fragments using the spread operator
Single API request
to Sanity fetches everything the page needs
Full type safety
ensures each component gets exactly the right data
Component isolation
is maintained - each component only accesses its fragment
This is fragment colocation in action. You get the modularity of component-level data requirements with the efficiency of a single API request.
Start your development server:
Visit http://localhost:3000. Since this runs server-side, you won't see GraphQL requests in your browser's Network tab. Instead, you should see:
Fast page loads
with no client-side waterfall requests
One GraphQL request
happening server-side (visible in your terminal logs)
All page data
rendered immediately on first load
For deployed apps, you can monitor these server-side requests in Vercel's Observability tab under "External APIs".
If you see multiple requests or TypeScript errors, double-check:
- Your Sanity schema matches the fragments
- Environment variables are set correctly
- GraphQL introspection is up to date
Note: Make sure your Sanity documents exist. Create a Posts and Author document in your Sanity Studio.
* * *
Now let's tackle the real-world challenge: making Incremental Static Regeneration work reliably with your fragment-based architecture.
In a traditional setup with multiple API calls, ISR regeneration looks like this:
What goes wrong:
Multiple failure points
: If any single request fails, the entire page regeneration fails
Slow regeneration
: Sequential requests create delays
Unpredictable costs
: Request count scales with component complexity
At scale
: High-traffic sites may hit API rate limits during mass revalidation
With fragment colocation, ISR regeneration becomes predictable:
Why this works better:
Predictable API usage
: Exactly one request per page regeneration
Single point of failure
: Easier to handle and retry
Faster regeneration
: No waterfall delays
Cost control
: Request count is independent of component complexity
Scales reliably
: Stays within rate limits even at high traffic
Set up basic ISR for your post page:
For faster content updates, add webhook-based revalidation:
Add the webhook secret to your environment:
In your Sanity Studio, set up a webhook:
Go to
Manage
→
API
→
Webhooks
- Create a new webhook with:
URL
:
https://your-domain.com/api/revalidate?secret=your-secret-key-hereTrigger on
: Create, Update, Delete
Test your ISR setup:
- Deploy to production
Trigger a webhook
by updating content in Sanity Studio
Monitor the logs
for successful revalidation
Check response times
- should be consistently fast
Verify content updates
appear within seconds
* * *
You have built a Next.js application that uses GraphQL Fragment Colocation with Sanity CMS. The complete implementation is available in the template repository.
Here's what you accomplished:
Architecture Benefits:
Single API request per page
eliminates waterfall requests and rate limiting issues
Component-level data colocation
maintains clean, modular code
Full TypeScript safety
with compile-time validation of GraphQL operations
Reliable ISR regeneration
that works consistently in production
Technical Implementation:
gql.tada integration
for type-safe GraphQL documents
URQL with React Server Components
for efficient data fetching
Fragment composition patterns
that scale with your application
Webhook-based revalidation
for instant content updates
This approach can be applied to other content-heavy Next.js applications that need both good developer experience and optimal performance.
- The template repository contains the complete working implementation with detailed setup instructions.
- The gql.tada documentation has comprehensive guides for advanced GraphQL patterns and TypeScript integration.
- The Sanity Next.js documentation covers CMS-specific patterns and best practices.
- The URQL documentation provides detailed information about GraphQL client configuration and React Server Component integration.
- For Next.js ISR and App Router questions, the official Next.js documentation is your best resource.