How to Use Sanity with Astro: Complete Guide
Step-by-step guide to integrating Sanity with your Astro website. Installation, configuration, and best practices.
Sanity is a composable content platform with real-time collaboration and a ridiculously flexible schema system. Pair it with Astro and you get a content-driven site that is fast to build, fast to load, and easy for your team to manage. Astro has an official Sanity integration, @sanity/astro, which makes the setup straightforward.
Versions referenced in this guide (verified against the npm registry on 2026-05-29): Astro 6.4.2, @sanity/astro 3.4.0, @sanity/image-url 2.1.1, astro-portabletext 0.13.0, and the sanity Studio package 5.28.0.
Prerequisites
- Node.js 22.12.0 or newer (Astro 6 dropped Node 18 and 20)
- An Astro project (
npm create astro@latest) - A Sanity account and project (free tier available at sanity.io)
Installation
Install the official Astro Sanity integration. The astro add command from the official plugin docs also pulls in @astrojs/react, which the integration uses when you later embed Sanity Studio on a route:
npx astro add @sanity/astro @astrojs/react
Then add the image URL builder, which you will use for optimized images:
npm install @sanity/image-url
The astro add command automatically updates your astro.config.mjs and registers the React integration. After running it, the Sanity docs recommend adding the integration's types to your tsconfig.json so the sanity:client virtual module resolves cleanly:
// tsconfig.json
{
"compilerOptions": {
"types": ["@sanity/astro/module"]
}
}
Configuration
After running the add command, update your Astro config with your Sanity project details. The integration accepts the same options as @sanity/client plus the integration-specific projectId, dataset, apiVersion, and useCdn:
// astro.config.mjs
import { defineConfig } from "astro/config";
import sanity from "@sanity/astro";
import react from "@astrojs/react";
export default defineConfig({
integrations: [
sanity({
projectId: "your_project_id",
dataset: "production",
// For static builds, set useCdn: false. Content is fetched once
// at build time, so the CDN cache adds no benefit. Flip it to true
// only for on-demand (server-rendered) pages serving published content.
useCdn: false,
// Required for predictable query behavior. Pin a date, never leave it floating.
apiVersion: "2026-03-01",
}),
react(),
],
});
The official Sanity docs recommend useCdn: false for static builds. Older guides that hardcode useCdn: true are wrong for the default static output. You can find your project ID in the Sanity dashboard at manage.sanity.io.
A Note on Astro 6 Rendering
Astro defaults to fully static output. The separate output: 'hybrid' mode was removed in Astro 5 and merged into static, so do not set it. With static output, every page is pre-rendered to HTML at build time. If you want a specific page (for example a draft preview or a search endpoint) to render on demand, install an adapter and add export const prerender = false; to that page. The rest of the site stays static.
Basic Usage
With the integration installed, you can use the built-in sanityClient anywhere in your Astro components. Let's fetch a list of blog posts using GROQ, Sanity's query language:
---
// src/pages/blog/index.astro
import { sanityClient } from "sanity:client";
import BaseLayout from "../../layouts/BaseLayout.astro";
const posts = await sanityClient.fetch(
`*[_type == "post"] | order(publishedAt desc) {
title,
slug,
publishedAt,
excerpt,
"imageUrl": mainImage.asset->url
}`
);
---
<BaseLayout title="Blog">
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.slug.current}`}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</a>
</li>
))}
</ul>
</BaseLayout>
Fetching Data for Dynamic Pages
Generate individual post pages with getStaticPaths:
---
// src/pages/blog/[slug].astro
import { sanityClient } from "sanity:client";
import { PortableText } from "astro-portabletext";
import BaseLayout from "../../layouts/BaseLayout.astro";
export async function getStaticPaths() {
const posts = await sanityClient.fetch(
`*[_type == "post"]{ slug }`
);
return posts.map((post) => ({
params: { slug: post.slug.current },
}));
}
const { slug } = Astro.params;
const post = await sanityClient.fetch(
`*[_type == "post" && slug.current == $slug][0] {
title,
body,
publishedAt,
"imageUrl": mainImage.asset->url
}`,
{ slug }
);
---
<BaseLayout title={post.title}>
<article>
<h1>{post.title}</h1>
{post.imageUrl && <img src={post.imageUrl} alt={post.title} />}
<PortableText value={post.body} />
</article>
</BaseLayout>
For rendering Portable Text (Sanity's rich text format), use the astro-portabletext package. This is the package the Sanity team recommends for Astro projects in their agent-toolkit best-practices reference. Note that there is no @portabletext/astro package on npm, so do not try to install one. The official @portabletext/* family ships framework-specific renderers like @portabletext/react and the framework-agnostic @portabletext/to-html, but for native .astro components reach for astro-portabletext:
npm install astro-portabletext
Import the PortableText component and pass the body array to its value prop, exactly as shown in the dynamic page above:
---
import { PortableText } from "astro-portabletext";
---
<PortableText value={post.body} />
Production Tips
Enable the CDN. Set
useCdn: truein your config for production builds. This serves cached responses from Sanity's edge network and is significantly faster than hitting the API directly.
Use GROQ projections. Only fetch the fields you actually need. Instead of *[_type == "post"], project specific fields. It reduces payload size and speeds up queries.
Set up on-demand revalidation. Configure a Sanity webhook to trigger rebuilds when content changes. Point it at your deploy hook URL from Vercel or Netlify.
Optimize images with the image URL builder. Use @sanity/image-url to generate responsive, optimized image URLs with width, height, and format parameters built in.
Use Sanity's Visual Editing. The integration exposes a stega option for stega-encoded source maps, which power the click-to-edit overlays that let editors jump from the live site straight into the Studio. Because Astro components do not re-render on the client, the Sanity docs note that live, real-time updates may require a small React wrapper or the View Transitions API around the editable region.
Alternatives to Consider
- Contentful if you need enterprise-grade content infrastructure with strict content modeling and approval workflows.
- TinaCMS if you want a git-backed CMS with visual editing and Markdown/MDX support. Good for developer blogs.
- Keystatic if you prefer zero external services. Content lives in your git repo.
Common Errors and Fixes
Cannot find module 'sanity:client'or no types on the virtual module. Add the integration's types totsconfig.jsonwith"types": ["@sanity/astro/module"]. Thesanity:clientimport is a virtual module the integration injects, so TypeScript will not resolve it until the types are referenced.Cannot find package '@portabletext/astro'. That package does not exist on npm. Installastro-portabletextinstead and importPortableTextfrom it. Mixing up the package name is the single most common setup failure for this stack.Stale content on a static build even after publishing in Sanity. A static Astro site fetches content once at build time. Publishing in the Studio does not update a deployed static site on its own. Trigger a rebuild with a Sanity webhook pointed at your host's deploy hook, or move that route to on-demand rendering with
export const prerender = false;plus an adapter.Setting
output: 'hybrid'throws a config error. That mode was removed in Astro 5 and folded intostatic. Delete theoutputoption (static is the default) and opt individual pages into server rendering withexport const prerender = false;.On-demand pages fail to build or 404 in production. Any page using
export const prerender = false;requires an adapter (Node, Vercel, Netlify, Cloudflare). Without one, Astro has no server runtime to render the route. Install the adapter for your host first.projectId can only contain a-z, 0-9 and dashesat build time. You left a placeholder likeyour_project_idin the config. Underscores are invalid. Paste the real project ID from manage.sanity.io.Empty results from a known-good query. Confirm
apiVersionis pinned to a real date (for example2026-03-01). An unpinned or future-dated API version can change query behavior. Also check thatdatasetmatches the dataset you published into (oftenproduction).
Official Docs and Examples
- Sanity and Astro introduction: https://www.sanity.io/docs/astro/introduction
- Configuring
@sanity/astro: https://www.sanity.io/docs/astro/configure-sanity-astro - The
@sanity/astroplugin page: https://www.sanity.io/plugins/sanity-astro - Build your blog with Astro and Sanity (full walkthrough): https://www.sanity.io/docs/developer-guides/sanity-astro-blog
- Astro official CMS guide for Sanity: https://docs.astro.build/en/guides/cms/sanity/
- Source and example folder for the integration: https://github.com/sanity-io/sanity-astro
Wrapping Up
Sanity and Astro are one of the best CMS and framework combos for content sites in 2026. The official integration handles the boring setup, GROQ makes querying intuitive, and the Studio gives your content team a great editing experience. Pin your apiVersion, keep useCdn: false for static builds, render rich text with astro-portabletext, and you are on solid, documented ground.
Sources
All versions and facts below were checked on 2026-05-29.
@sanity/astrolatest version 3.4.0: https://registry.npmjs.org/@sanity/astro/latest@sanity/image-urllatest version 2.1.1: https://registry.npmjs.org/@sanity/image-url/latestastro-portabletextlatest version 0.13.0: https://registry.npmjs.org/astro-portabletext/latestastrolatest version 6.4.2: https://registry.npmjs.org/astro/latestsanityStudio latest version 5.28.0: https://registry.npmjs.org/sanity/latest@sanity/clientlatest version 7.22.1: https://registry.npmjs.org/@sanity/client/latest- Confirmation that
@portabletext/astrois not published (registry returns "Not found"): https://registry.npmjs.org/@portabletext/astro - Configuring
@sanity/astro(useCdn guidance, options, virtual module): https://www.sanity.io/docs/astro/configure-sanity-astro @sanity/astroplugin page (install commandnpx astro add @sanity/astro @astrojs/react, quickstart config): https://www.sanity.io/plugins/sanity-astro- Sanity agent-toolkit Astro best practices (recommends
astro-portabletext,useCdn: false,@sanity/astro/moduletypes): https://github.com/sanity-io/agent-toolkit/blob/main/skills/sanity-best-practices/references/astro.md astro-portabletextinstall andPortableTextusage: https://github.com/theisel/astro-portabletext- Astro on-demand rendering (static is default, adapter required,
prerender = false): https://docs.astro.build/en/guides/on-demand-rendering/ - Astro 5 release notes (hybrid merged into static): https://astro.build/blog/astro-5/
- Astro 6 upgrade guide (Node.js 22.12.0 minimum, Node 18 and 20 dropped): https://docs.astro.build/en/guides/upgrade-to/v6/
- Astro official Sanity CMS guide: https://docs.astro.build/en/guides/cms/sanity/
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.