Fetching Sanity data in a component outside of /pages for a site-builder setup
For your site-builder setup, you have a few clean options depending on whether you're using Next.js App Router or Pages Router:
App Router (Recommended for Next.js 13+)
If you're on the App Router, you can fetch data directly in your component if it's a Server Component (the default):
// ArticleListingComponent.js
import { client } from '@/sanity/lib/client'
export default async function ArticleListingComponent() {
const articles = await client.fetch(
`*[_type == "article"][0...5]`,
{},
{ next: { revalidate: 60 } } // Optional: ISR
)
return (
<div>
{articles.map(article => (
<div key={article._id}>{article.title}</div>
))}
</div>
)
}This is the cleanest approach - each component fetches exactly what it needs, and Next.js automatically deduplicates identical requests during rendering.
Pages Router
If you're using the Pages Router, you have two main approaches:
1. Fetch Everything in getStaticProps (Recommended)
Since getStaticProps only works at the page level, fetch all data your page builder needs in your page component and pass it down:
// pages/[slug].js
export async function getStaticProps({ params }) {
const page = await client.fetch(`*[_type == "page" && slug.current == $slug][0]`,
{ slug: params.slug }
)
// Fetch additional data for specific components
const articles = await client.fetch(`*[_type == "article"][0...5]`)
return {
props: { page, articles },
revalidate: 60
}
}
function Page({ page, articles }) {
return (
<>
{page.components.map(component => {
if (component._type === 'articleListing') {
return <ArticleListingComponent articles={articles} key={component._key} />
}
// other components...
})}
</>
)
}2. Client-Side Fetching
For truly dynamic components, you can fetch on the client side using the Sanity client with React hooks:
// ArticleListingComponent.js
import { useEffect, useState } from 'react'
import { client } from '@/sanity/lib/client'
export default function ArticleListingComponent() {
const [articles, setArticles] = useState([])
useEffect(() => {
client.fetch(`*[_type == "article"][0...5]`)
.then(setArticles)
}, [])
return (
<div>
{articles.map(article => (
<div key={article._id}>{article.title}</div>
))}
</div>
)
}This works but loses the SEO benefits of pre-rendering. You could also use picosanity for a smaller bundle if you only need read operations.
For Modular Page Builders
Since you mentioned a drag-and-drop page builder, you're likely implementing something similar to Sanity's content blocks pattern. The cleanest approach is:
- Define your page schema with an array of component blocks
- Fetch the page data at the page level (including any data needed by child components)
- Map over the blocks and render the appropriate component
This keeps data fetching centralized and predictable, which is especially important for static generation. If specific components need fresh data, you can use Incremental Static Regeneration with the revalidate option to keep content reasonably up-to-date without full client-side fetching.
Show original thread8 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.