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.
Install a new React Router 8 (Remix) application
If you have an existing application, skip this first step and adapt the rest of the guide 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 8 application with Tailwind CSS and TypeScript.
You should now have your Studio and React Router 8 application in two separate, adjacent folders:
# outside your studio directory
npx create-react-router@latest react-router-hello-world -y
cd react-router-hello-world# outside your studio directory
pnpm dlx create-react-router@latest react-router-hello-world -y
cd react-router-hello-world# outside your studio directory
yarn dlx create-react-router@latest react-router-hello-world -y
cd react-router-hello-world# outside your studio directory
bunx create-react-router@latest react-router-hello-world -y
cd react-router-hello-world├─ /react-router-hello-world
└─ /studio-hello-worldInstall 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@tailwindcss/typographyto style rendered Portable Text with theproseclass
# in react-router-hello-world
npm install @sanity/client @sanity/image-url @portabletext/react @tailwindcss/typography# in react-router-hello-world
pnpm add @sanity/client @sanity/image-url @portabletext/react @tailwindcss/typography# in react-router-hello-world
yarn add @sanity/client @sanity/image-url @portabletext/react @tailwindcss/typography# in react-router-hello-world
bun add @sanity/client @sanity/image-url @portabletext/react @tailwindcss/typographyStart the development server
Run the following command and open http://localhost:5173 in your browser.
# in react-router-hello-world
npm run dev# in react-router-hello-world
pnpm run dev# in react-router-hello-world
yarn run dev# in react-router-hello-world
bun run devConfigure 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: "2026-05-15",
useCdn: false,
});Display 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>
);
}Display individual posts
Create a new route for individual post pages.
The dynamic value of a slug when visiting /:slug 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 to style the post’s body content. We installed @tailwindcss/typography in the dependencies step. Enable it by adding @plugin "@tailwindcss/typography"; to app/app.css below the existing @import "tailwindcss"; line.
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/post";
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?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?