CoursesContent-driven web application foundationsFetch Sanity Content
Track
Work-ready Next.js

Content-driven web application foundations

Log in to watch a video walkthrough of this lesson
Video thumbnail

Query for your content using Sanity Client, a library compatible with the Next.js cache and React Server Components for modern, integrated data fetching.

Log in to mark your progress for each Lesson and Task

Sanity content is typically queried with GROQ queries from a configured Sanity Client. Fortunately, one has already been created for you.

Open src/sanity/lib/client.ts to confirm it exists in your project.

Queries performed with Sanity Client are compatible with Next.js caching features, React Server Components, and the App Router.

It also provides ways to interact with Sanity projects and even write content back to the Content Lake with mutations. You'll use some of these features in later lessons.

It's time to put everything we've set up to work. In this lesson, you'll create a route to serve as a Post index page and a dynamic route to display an individual post.

For now, you'll focus on data fetching at the top of each route. React Server Components allow you to perform fetches from inside individual components. Future lessons may address where this is beneficial. For now, our queries are simple enough – and GROQ is expressive enough – to get everything we need at the top of the tree.

See the Next.js App Router documentation for more details about file-based routing and how file and folder names impact URLs

The most significant change we'll make first is creating a separate "Route Group" for the entire application front end. This route group will separate the front end layout code from the Studio without affecting the URL. It is also useful when integrating Visual Editing and displaying the front end inside the Studio.

Create a new (frontend) directory and duplicate layout.tsx into it
mkdir -p src/app/\(frontend\) && cp src/app/layout.tsx src/app/\(frontend\)/

You should now have two layout.tsx files inside the app folder at these locations:

src
└── app
├── // all other files
├── layout.tsx
└── frontend
└── layout.tsx

Later in this track, the home page will become fully featured. For now, it just needs a link to the posts index.

Move page.tsx into the (frontend) folder
Update your home page route to add basic navigation to the posts index.
src/app/(frontend)/page.tsx
import Link from "next/link";
export default async function Page() {
return (
<section className="container mx-auto grid grid-cols-1 gap-6 p-12">
<h1 className="text-4xl font-bold">Home</h1>
<hr />
<Link href="/posts">Posts index &rarr;</Link>
</section>
);
}
Next.js provides the <Link /> component as an enhancement to the HTML anchor (<a>) element.

You should now have a basic home page like this:

This page will list up to 12 of the latest post documents. Inside this route:

  • The configured Sanity Client is imported as client
  • The GROQ query POSTS_QUERY is used by client.fetch
  • Thanks to automatic type inference, the response will be typed POSTS_QUERYResult
Create a new directory for a post-index page to fetch all post type documents
src/app/(frontend)/posts/page.tsx
import Link from "next/link";
import { client } from "@/sanity/lib/client";
import { POSTS_QUERY } from "@/sanity/lib/queries";
const options = { next: { revalidate: 60 } };
export default async function Page() {
const posts = await client.fetch(POSTS_QUERY, {}, options);
return (
<main className="container mx-auto grid grid-cols-1 gap-6 p-12">
<h1 className="text-4xl font-bold">Post index</h1>
<ul className="grid grid-cols-1 divide-y divide-blue-100">
{posts.map((post) => (
<li key={post._id}>
<Link
className="block p-4 hover:text-blue-500"
href={`/posts/${post?.slug?.current}`}
>
{post?.title}
</Link>
</li>
))}
</ul>
<hr />
<Link href="/">&larr; Return home</Link>
</main>
);
}
Next.js supports React Server Components, which allow you to fetch and await data within the component. Read more on the Next.js documentation.

The options variable passed into the Sanity Client is a light configuration for Next.js caching. You should know that with these settings, the cache has been configured to only update pages at most every 60 seconds.

Your content authors would like to see changes to their content appear immediately in the web application. Making uncached requests directly to the Sanity API will do this, but responses are slower and can become expensive depending on your traffic.

Your end users want to see pages load as fast as possible, and leveraging Next.js cache and Sanity's CDN makes that possible.

Finding the right balance is a complex topic, and there are ways to mitigate the concerns above and find a solution for everyone. Caching is covered in another course in this track: Controlling cached content in Next.js.

It is best to add some configuration and modify it later, so 60 seconds is a sensible start.

The GROQ query included a filter to ensure only documents with a slug.current was defined – but the TypeGen generated a type where slug.current could be null.

This is a known limitation of TypeGen while it is in beta and should be resolved in a future release.

You should now have a post index page at http://localhost:3000/posts like this:

The GROQ query POST_QUERY used a variable $slug to match a route with a post in the dataset. For this, you can use a "Dynamic Route," where a segment in the URL is made available to the server component for the route as a prop.

Read more about Next.js Dynamic Routes on their documentation

So, for example, because you're creating a route at:

src/app/(frontend)/posts/[slug]/page.tsx

If you visited the URL:

http://localhost:3000/posts/hello-world

The route would have this params object in its props:

{ "slug": "hello-world" }

Which can then be passed into Sanity Client to match the value of slug to a value in a document.

Create a new route for an individual post
src/app/(frontend)/posts/[slug]/page.tsx
import { client } from '@/sanity/lib/client'
import { POST_QUERY } from '@/sanity/lib/queries'
import { notFound } from 'next/navigation'
import Link from 'next/link'
type PostIndexProps = { params: { slug: string } }
const options = { next: { revalidate: 60 } }
export default async function Page({ params }: PostIndexProps) {
const post = await client.fetch(POST_QUERY, params, options)
if (!post) {
notFound()
}
return (
<main className="container mx-auto grid grid-cols-1 gap-6 p-12">
<h1 className="text-4xl font-bold text-balance">{post?.title}</h1>
<hr />
<Link href="/posts">&larr; Return to index</Link>
</main>
);
}

You should now be able to click any of the links on the posts index page and see the title of a blog post with a link back to the index:

You now have a basic – but functional – web application. It's currently trapped in your local development environment. And while it isn't much, it's an excellent habit to deploy early and often so you can get into a habit of continuous improvement.

You'll deploy your web application to the world in the following lessons.

Courses in the "Work-ready Next.js" track