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:
- Node.js 20+ and
npm/yarn/pnpm - A Sanity project 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 supporturql: 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 theauthorFragmentusing...AuthorandpostHeaderFragmentusing...PostHeader- The 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-here - Trigger on: Create, Update, Delete
- URL:
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.