Displaying content in a React Router front end
You’ve configured your Studio with a post document type and learned how to query from your hosted dataset. Before deploying the Studio, let’s query and display this content on the front-end framework of your choice.

1Install a new React Router 7 (Remix) application
If you have an existing application, skip this first step and adapt the rest of the lesson to install Sanity dependencies to fetch and render content.
Run the following in a new tab or window in your Terminal (keep the Studio running) to create a new React Router 7 application with Tailwind CSS and TypeScript.
You should now have your Studio and React Router 7 application in two separate, adjacent folders:
# outside your studio directory
npx create-react-router@latest react-router-{{PROJECT_NAME_SLUGIFIED}} -y
cd react-router-{{PROJECT_NAME_SLUGIFIED}}# outside your studio directory
pnpm dlx create-react-router@latest react-router-{{PROJECT_NAME_SLUGIFIED}} -y
cd react-router-{{PROJECT_NAME_SLUGIFIED}}# outside your studio directory
yarn dlx create-react-router@latest react-router-{{PROJECT_NAME_SLUGIFIED}} -y
cd react-router-{{PROJECT_NAME_SLUGIFIED}}# outside your studio directory
bunx create-react-router@latest react-router-{{PROJECT_NAME_SLUGIFIED}} -y
cd react-router-{{PROJECT_NAME_SLUGIFIED}}├─ /react-router-{{PROJECT_NAME_SLUGIFIED}}
└─ /studio-{{PROJECT_NAME_SLUGIFIED}}2Install Sanity dependencies
Run the following inside the react-router-hello-world directory to install:
@sanity/clientfor fetching content from Sanity@sanity/image-urlhelper functions to take image data from Sanity and create a URL@portabletext/reactto render Portable Text as React components
# in react-router-{{PROJECT_NAME_SLUGIFIED}}
npm install @sanity/client @sanity/image-url @portabletext/react# in react-router-{{PROJECT_NAME_SLUGIFIED}}
pnpm add @sanity/client @sanity/image-url @portabletext/react# in react-router-{{PROJECT_NAME_SLUGIFIED}}
yarn add @sanity/client @sanity/image-url @portabletext/react# in react-router-{{PROJECT_NAME_SLUGIFIED}}
bun add @sanity/client @sanity/image-url @portabletext/react3Start the development server
Run the following command and open http://localhost:5173 in your browser.
# in react-router-{{PROJECT_NAME_SLUGIFIED}}
npm run dev# in react-router-{{PROJECT_NAME_SLUGIFIED}}
pnpm run dev# in react-router-{{PROJECT_NAME_SLUGIFIED}}
yarn run dev# in react-router-{{PROJECT_NAME_SLUGIFIED}}
bun run dev4Configure the Sanity client
To fetch content from Sanity, you’ll first need to configure a Sanity Client.
Create a directory react-router-hello-world/app/sanity and within it create a client.ts file, with the following code:
import { createClient } from "@sanity/client";
export const client = createClient({
projectId: "your-project-id",
dataset: "production",
apiVersion: "2024-01-01",
useCdn: false,
});5Display content on the home page
React Router uses a loader function exported from routes for server-side fetching of data. Routes are configured in the app/routes.ts file.
The default home page can be found at app/routes/home.tsx
Update it to render a list of posts fetched from your Sanity dataset using the code below.
import type { SanityDocument } from "@sanity/client";
import { Link } from "react-router";
import { client } from "~/sanity/client";
import type { Route } from "./+types/home";
const POSTS_QUERY = `*[
_type == "post"
&& defined(slug.current)
]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt}`;
export async function loader() {
return { posts: await client.fetch<SanityDocument[]>(POSTS_QUERY) };
}
export default function IndexPage({ loaderData }: Route.ComponentProps) {
const { posts } = loaderData;
return (
<main className="container mx-auto min-h-screen max-w-3xl p-8">
<h1 className="text-4xl font-bold mb-8">Posts</h1>
<ul className="flex flex-col gap-y-4">
{posts.map((post) => (
<li className="hover:underline" key={post._id}>
<Link to={`/${post.slug.current}`}>
<h2 className="text-xl font-semibold">{post.title}</h2>
<p>{new Date(post.publishedAt).toLocaleDateString()}</p>
</Link>
</li>
))}
</ul>
</main>
);
}6Display individual posts
Create a new route for individual post pages.
The dynamic value of a slug when visiting /:post in the URL is used as a parameter in the GROQ query used by Sanity Client.
Notice that we’re using Tailwind CSS Typography’s prose class name to style the post’s body block content. Install it in your project following their documentation.
Update the routes.ts configuration file to load this route when individual post links are clicked.
import { Link } from "react-router";
import { createImageUrlBuilder, type SanityImageSource } from "@sanity/image-url";
import type { SanityDocument } from "@sanity/client";
import { PortableText } from "@portabletext/react";
import type { Route } from "../+types/root";
import { client } from "~/sanity/client";
const { projectId, dataset } = client.config();
const urlFor = (source: SanityImageSource) =>
projectId && dataset
? createImageUrlBuilder({ projectId, dataset }).image(source)
: null;
const POST_QUERY = `*[_type == "post" && slug.current == $slug][0]`;
export async function loader({ params }: Route.LoaderArgs) {
return { post: await client.fetch<SanityDocument>(POST_QUERY, params) };
}
export default function Component({ loaderData }: Route.ComponentProps) {
const { post } = loaderData;
const postImageUrl = post.image
? urlFor(post.image)?.width(550).height(310).url()
: null;
return (
<main className="container mx-auto min-h-screen max-w-3xl p-8 flex flex-col gap-4">
<Link to="/" className="hover:underline">
← Back to posts
</Link>
{postImageUrl && (
<img
src={postImageUrl}
alt={post.title}
className="aspect-video rounded-xl"
width="550"
height="310"
/>
)}
<h1 className="text-4xl font-bold mb-8">{post.title}</h1>
<div className="prose">
<p>Published: {new Date(post.publishedAt).toLocaleDateString()}</p>
{Array.isArray(post.body) && <PortableText value={post.body} />}
</div>
</main>
);
}import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [
index("routes/home.tsx"),
route("/:slug", "routes/post.tsx"),
] satisfies RouteConfig;Was this page helpful?