Query content in Next.js
Use the next-sanity library to write typed GROQ queries with defineQuery and fetch content in App Router or Pages Router.
The next-sanity library wraps @sanity/client with helpers designed specifically for Next.js. Use it to write typed GROQ queries, fetch content in both App Router and Pages Router, and enable live content updates. It bundles:
- GROQ helpers that make TypeGen setup easier.
- Live content and visual editing tooling for fetching fresh content from different perspectives.
Prerequisites
To follow this guide, you need:
next-sanity13 or later, which requires Next.js 16 or later.- A Sanity client (
client.ts) and a live content setup (live.ts) in asanity/lib/folder. Runningnpx sanity@latest initin your Next.js project creates both. - Optional: Sanity TypeGen, if you want TypeScript to infer query result types.
Write and type GROQ queries
Write queries with defineQuery
Use defineQuery to write GROQ queries with TypeGen support and syntax highlighting:
// src/sanity/lib/queries.ts
import { defineQuery } from 'next-sanity'
export const POSTS_QUERY = defineQuery(
`*[_type == "post" && defined(slug.current)][0...12]{
_id, title, slug
}`
)
export const POST_QUERY = defineQuery(
`*[_type == "post" && slug.current == $slug][0]{
title, body, mainImage
}`
)defineQuery enables two things:
- Automatic type inference: when you pass a
defineQueryresult toclient.fetch, TypeScript infers the return type from the query once you generate types with Sanity TypeGen. No manual type annotations needed. - Syntax highlighting: with the Sanity VS Code extension installed, GROQ inside
defineQuerygets full syntax highlighting.
next-sanity also exports a groq template tag for backward compatibility, but defineQuery is recommended for new projects.
TypeGen setup
Sanity TypeGen generates TypeScript types for your schema and GROQ query results. If you used sanity init with an embedded Studio, generate types in two steps. Extract your schema with npx sanity@latest schema extract, then generate types with npx sanity@latest typegen generate. For manual setup, see the TypeGen documentation.
Fetch content
Choose a fetch method
Recommended: defineLive's sanityFetch. If you're using the Live Content API (most apps should), import sanityFetch from your live.ts file. This function tags each request for cache revalidation, and <SanityLive /> uses those tags to refresh content when it changes. With Cache Components enabled, sanityFetch doesn't create a cache boundary, so call it inside your own 'use cache' function. See the Visual Editing or Live Content guide.
Alternative: manual sanityFetch helper. For apps that don't use the Live Content API, you build a wrapper around client.fetch with explicit caching options. See Caching and revalidation.
Basic: client.fetch directly. Works for simple cases, but you must pass caching options such as next.revalidate on every call. See the next-sanity client configuration for additional settings.
App Router
The following example uses client.fetch directly in a Server Component. This works for prototyping. For production, use one of the sanityFetch approaches in "Choose a fetch method":
// src/app/page.tsx
import { client } from '@/sanity/lib/client'
import { POSTS_QUERY } from '@/sanity/lib/queries'
export default async function PostIndex() {
const posts = await client.fetch(POSTS_QUERY)
return (
<ul>
{posts.map((post) => (
<li key={post._id}>
<a href={`/posts/${post?.slug?.current}`}>{post?.title}</a>
</li>
))}
</ul>
)
}Pages Router
Use getStaticProps to fetch data at build time. This example includes revalidate: 60 to enable ISR, refreshing the data every 60 seconds. For ISR, set useCdn: false on the client you fetch with, so revalidation doesn't return content cached by the Sanity CDN:
// src/pages/index.tsx
import type { InferGetStaticPropsType } from 'next'
import { client } from '@/sanity/lib/client'
import { POSTS_QUERY } from '@/sanity/lib/queries'
export async function getStaticProps() {
const posts = await client.fetch(POSTS_QUERY)
return { props: { posts }, revalidate: 60 }
}
export default function PostIndex({
posts,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<ul>
{posts.map((post) => (
<li key={post._id}>
<a href={`/posts/${post?.slug?.current}`}>{post?.title}</a>
</li>
))}
</ul>
)
}The next-sanity live helpers (defineLive, SanityLive) are App Router only. Pages Router apps can use client.fetch directly with ISR (revalidate in getStaticProps) for cache management.
Next steps
Continue with these guides:
- To keep content updated as editors publish, see Add live content to your application.
- To control how long content stays cached, see Caching and revalidation in Next.js.
- To add click-to-edit overlays, see Visual Editing with Next.js App Router.
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?