

Step-by-Step Sanity Guide: Setting Up Sanity.io with Next.js
Part 1: Step-by-Step Sanity Guide: Setting Up Sanity.io with Next.js
Part 2: Step-by-Step Sanity Guide: Adding Code Blocks to Your Sanity + Next.js Blog
Introduction
In this tutorial, we'll walk you through the process of integrating Sanity.io (a headless CMS) with a Next.js application. This powerful combination allows you to manage your content efficiently while building a performant React frontend.
Prerequisites
- Basic knowledge of Next.js
- Node.js installed on your machine
- Visual Studio Code installed on your machine
- A Sanity.io account (free tier available)
Step 1: Create a New Next.js App
Initialize your Next.js application by running the project creation command, which will launch an interactive setup process to configure your blog foundation
1npx create-next-app@latest my-sanity-app
Configure your project settings as the setup wizard presents you with various options for TypeScript, ESLint, Tailwind CSS, and other essential features.
- Would you like to use TypeScript? ... Yes
- Would you like to use ESLint? … Yes
- Would you like to use Tailwind CSS? ... Yes
- Would you like your code inside a `src/` directory? ... Yes
- Would you like to use App Router? (recommended) ... Yes
- Would you like to use Turbopack for `next dev`? ... Yes
- Would you like to customize the import alias (`@/*` by default)? ... Yes
- What import alias would you like configured? ... @/*
Open your development environment by launching the newly created project in Visual Studio Code, where you'll begin building your Sanity-powered blog with a clean, organized codebase ready for development.
1cd my-sanity-app
2code .
Step 2: Initialize Sanity in Your Project
Run the initialization command in your project root directory to begin setting up Sanity integration with your Next.js application.
1npx sanity@latest init
Follow the interactive setup process as the command will present you with various configuration options and suggestions tailored to your project's needs.
- Create a new project or select an existing one ... Create new project
- Your project name ... My Sanity Project
- Select organization ... Create new organization
- Organization name ... <Your-Name>
- Use the default dataset configuration? ... Yes
- Would you like to add configuration files for a Sanity project in this Next.js folder ... Yes
- Do you want to use TypeScript? ... Yes
- Would you like an embedded Sanity Studio? ... Yes
- What route do you want to use for the Studio? ... /studio
- Select project template to use ... Blog (schema)
- Would you like to add the project ID and dataset to your .env.local file ... Yes
Complete the setup and verify that all necessary Sanity files and configurations have been properly generated in your project structure, preparing your development environment for content management.
Step 3: Launch and Access Sanity Studio
Start your development server by running your Next.js project and navigating to http://localhost:3000/studio in your browser.
1npm run dev
Complete the authentication process when the Sanity login page appears. Sign in using your existing Sanity account credentials to gain access to your content management interface.

Enter your content creation workspace as you'll be automatically redirected to the Sanity Studio dashboard after successful login. This intuitive interface is where you'll create, edit, and manage all your blog content.

Begin content creation you now have full access to your personalized CMS where you can start building your blog posts, managing media assets, and organizing your content with ease!
Step 4: Create Your First Content
Set up your content foundation and craft your inaugural blog post by creating an author profile and category within Sanity Studio, publishing them to establish the building blocks for your content.

Navigate to the post creation interface and complete all the required fields with your blog content, taking advantage of Sanity's rich text editor and media management features.
Once your post is ready, publish it to make the content live and available for your Next.js application to fetch from Sanity's content lake.

Content creation complete! You've successfully established your content workflow and created your first publishable post. Your CMS is now populated and ready for the next development phase.
Step 5: Create GROQ Queries and Optimize Content Delivery
Set up your data fetching layer by creating a queries.ts file within your sanity/lib directory. This file will house all your GROQ queries for retrieving blog posts and other content from Sanity.
1export const getAllPostsQuery = `
2 *[_type == "post"] {
3 title,
4 "slug": slug.current,
5 "author": author->name,
6 body,
7 "mainImage": {"url": mainImage.asset->url, "alt": mainImage.alt},
8 }
9`;
10
Integrate your queries seamlessly into your Next.js pages and components to fetch and display your content dynamically.
1import { client } from "@/sanity/lib/client";
2import { getAllPostsQuery } from "@/sanity/lib/queries";
3import { PortableTextBlock, PortableText } from "next-sanity";
4import Image from "next/image";
5
6type PostType = {
7 author: string;
8 body: PortableTextBlock[];
9 mainImage: { alt: string; url: string };
10 slug: string;
11 title: string;
12};
13
14export default async function Home() {
15 const posts: PostType[] | null = await client.fetch(getAllPostsQuery);
16 if (!posts) return <div>Error while fetching posts</div>;
17 return (
18 <section>
19 {posts.map(({ title, slug, mainImage: { alt, url }, author, body }) => (
20 <article key={slug}>
21 <h1>{title}</h1>
22 <span>made by: {author}</span>
23 <Image src={url} alt={alt} width={720} height={540} />
24 <PortableText value={body} />
25 </article>
26 ))}
27 </section>
28 );
29}
Configure image optimization by adding Sanity's CDN domain to your Next.js configuration file. This crucial step enables automatic image optimization and ensures your media assets load quickly across all devices.
1import type { NextConfig } from "next";
2
3const nextConfig: NextConfig = {
4 /* config options here */
5 images: {
6 remotePatterns: [
7 {
8 protocol: "https",
9 hostname: "cdn.sanity.io",
10 },
11 ],
12 },
13};
14
15export default nextConfig;
16
Enjoy the performance benefits as your content now leverages Sanity's lightning-fast global CDN combined with Next.js's built-in image optimization. Your blog will deliver content efficiently to users worldwide.

Scale your content effortlessly simply create and publish new posts through Sanity Studio, and they'll automatically appear on your website without any additional configuration needed!

Step 6: Refactor and Add Styles Your Blog Structure
Organize your post schema by moving the post type definition to a dedicated types folder.
1import { PortableTextBlock } from "next-sanity";
2
3export type PostType = {
4 author: string;
5 body: PortableTextBlock[];
6 mainImage: { alt: string; url: string };
7 slug: string;
8 title: string;
9};
10
Build your blog component to create a reusable, well-structured component that will elegantly display your blog posts with proper styling and functionality.
1import { PostType } from "@/types/post";
2import { PortableText } from "next-sanity";
3import Image from "next/image";
4import React from "react";
5
6type BlogProps = {
7 post: PostType;
8};
9
10export const Blog = ({
11 post: {
12 title,
13 author,
14 mainImage: { url, alt },
15 body,
16 },
17}: BlogProps) => {
18 return (
19 <article className="flex flex-col gap-4">
20 <h1 className="text-center text-4xl font-bold text-yellow-300">
21 {title}
22 </h1>
23 <span className="self-end capitalize font-semibold">
24 made by: <span className="text-orange-300">{author}</span>
25 </span>
26 <div className="relative aspect-[3/1.75]">
27 <Image
28 className="object-cover rounded-3xl"
29 src={url}
30 alt={alt}
31 priority
32 fill
33 />
34 </div>
35 <PortableText
36 value={body}
37 components={{
38 block: {
39 normal: ({ children }) => (
40 <p className="leading-7 sm:text-lg">{children}</p>
41 ),
42 h3: ({ children }) => (
43 <h2 className="text-pink-300 font-semibold tracking-tight text-xl sm:text-3xl">
44 {children}
45 </h2>
46 ),
47 },
48 list: {
49 bullet: ({ children }) => (
50 <ul className="sm:text-lg list-disc ml-6">{children}</ul>
51 ),
52 },
53 listItem: {
54 bullet: ({ children }) => (
55 <li className="marker:font-bold">{children}</li>
56 ),
57 },
58 }}
59 />
60 </article>
61 );
62};
63
Update your home page by integrating the new blog component and applying the necessary changes to showcase your posts effectively on the main landing area.
1import { Blog } from "@/components/blog";
2import { client } from "@/sanity/lib/client";
3import { getAllPostsQuery } from "@/sanity/lib/queries";
4import { PostType } from "@/types/post";
5
6export default async function Home() {
7 const posts: PostType[] | null = await client.fetch(getAllPostsQuery);
8 if (!posts) return <div>Error while fetching posts</div>;
9 return (
10 <section className="flex flex-col gap-10 max-w-3xl mx-auto py-10 px-5">
11 {posts.map((post) => (
12 <Blog key={post.slug} post={post} />
13 ))}
14 </section>
15 );
16}
17
Preview your results to see your blog come to life! Your posts will now display beautifully with the refined structure, proper styling, and seamless integration between Sanity CMS and your Next.js frontend.

Your blog is now ready to showcase your content with a professional, scalable architecture that's easy to maintain and expand.
Step 7: Create Individual Post Pages with Dynamic Routing
Add navigation links to your home page by implementing clickable links for each blog post, enabling users to navigate to individual post pages seamlessly.
1import { Blog } from "@/components/blog";
2import { client } from "@/sanity/lib/client";
3import { getAllPostsQuery } from "@/sanity/lib/queries";
4import { PostType } from "@/types/post";
5import Link from "next/link";
6
7export default async function Home() {
8 const posts: PostType[] | null = await client.fetch(getAllPostsQuery);
9 if (!posts) return <div>Error while fetching posts</div>;
10 return (
11 <section className="flex flex-col gap-10 max-w-3xl mx-auto py-10 px-5">
12 {posts.map((post) => (
13 <div className="flex flex-col gap-2" key={post.slug}>
14 <Blog post={post} />
15 <Link
16 className="text-blue-300 self-end underline-offset-2 hover:underline"
17 href={`/posts/${post.slug}`}
18 >
19 See post »
20 </Link>
21 </div>
22 ))}
23 </section>
24 );
25}
26
Create a targeted GROQ query to fetch specific posts by their slug parameter, allowing you to retrieve the exact content needed for each individual post page.
1export const getAllPostsQuery = `
2 *[_type == "post"] {
3 title,
4 "slug": slug.current,
5 "author": author->name,
6 body,
7 "mainImage": {"url": mainImage.asset->url, "alt": mainImage.alt},
8 }
9`;
10
11export const getPostBySlugQuery = (slug: string) => `
12 *[_type == "post" && slug.current == "${slug}" ] {
13 title,
14 "slug": slug.current,
15 "author": author->name,
16 body,
17 "mainImage": {"url": mainImage.asset->url, "alt": mainImage.alt},
18 }[0]`;
19
Build your dynamic blog page to display individual posts with full content, proper formatting, and enhanced readability for your readers.
1import { Blog } from "@/components/blog";
2import { client } from "@/sanity/lib/client";
3import { getPostBySlugQuery } from "@/sanity/lib/queries";
4import { PostType } from "@/types/post";
5import { notFound } from "next/navigation";
6import React from "react";
7
8type PostsProps = {
9 params: Promise<{ slug: string }>;
10};
11
12export default async function Posts({ params }: PostsProps) {
13 const { slug } = await params;
14 const post: PostType | null = await client.fetch(getPostBySlugQuery(slug));
15 if (!post) notFound();
16 return (
17 <section className="max-w-3xl mx-auto py-10 px-5">
18 <Blog post={post} />
19 </section>
20 );
21}
22
Leverage Next.js dynamic routing as your new setup automatically handles URL parameters, creating clean, SEO-friendly URLs for each post while maintaining fast navigation and optimal user experience.

Your blog now features complete navigation between your post listing and individual post pages, creating a fully functional blogging experience!
Conclusion
Congratulations! You've successfully integrated Sanity.io with your Next.js application, creating a powerful content management system that combines the best of both worlds. Through this tutorial, you've accomplished several key milestones
What You've Built
- A fully functional Next.js application with TypeScript and Tailwind CSS
- An embedded Sanity Studio for content management accessible at /studio
- A complete blog system with authors, categories, and posts
- Dynamic routing for individual blog posts using slugs
- Optimized image handling through Sanity's CDN and Next.js image optimization
- GROQ queries for efficient data fetching
This foundation gives you a robust, modern web development stack that separates content management from presentation, allowing for greater flexibility and maintainability as your project evolves. You now have the tools to build sophisticated, content-driven applications with ease.
Happy coding! 🚀
Part 1: Step-by-Step Sanity Guide: Setting Up Sanity.io with Next.js
Part 2: Step-by-Step Sanity Guide: Adding Code Blocks to Your Sanity + Next.js Blog