How to use Sanity CMS with Astro

by Paul Scanlon

The ability to select both the framework and the content management system that each have the best fit with the requirements of your project, the skills of your teams, and the needs of the users and maintainers, is a great advantage when planning your technical architecture and tools for a site. What does combining tools together in a composable architecture look like? Let’s explore using Sanity, an excellent content platform, with Astro, a powerful and flexible site generator and web framework.

#TL;DR

Within this guide, we’ll go in detail explaining all the steps to discover how to develop an Astro site using Sanity as well as the process for deploying it to Netlify.

Explore the example site and repo

You can see a preview of the deployed site and the repository for the code on the following links.

..or you can follow the steps below to create this example from the very beginning.

#Prerequisites

To follow along with all of the steps of this walk-through you’ll need:

#What is Sanity CMS?

Sanity is a headless CMS with a fully decoupled, real-time content back end and entirely customizable content workspaces.

#What is Sanity Studio?

Sanity Studio lives alongside your code and comes complete with a user interface that can be used by content editors to create, update or delete content that will be seen on your site. When you deploy your site, Sanity Studio is deployed with it.

The schema you define in your codebase using JavaScript, will determine the fields that are visible in the Sanity Studio interface and how you access these fields to display data on pages of your site.

There are a number of ways Sanity Studio can be integrated with your workflow.

  1. Standalone: Lives in it’s own repository
  2. Standalone: Lives in a directory within a mono-repo, with the site in another directory
  3. Standalone: Lives in its own directory within the site directory
  4. Embedded: Lives alongside the site code in the same directory

In this guide we’ll be using Sanity’s Astro Integration which sets Sanity Studio up as per option 4, above.

#Sanity and Astro

Astro avoids shipping any JavaScript to the client by default, and by using the sanity-astro integration you’ll be able to query data from Sanity and create static, or server-side rendered pages using Astro so that your pages are still able to be rendered without the need for client-side JavaScript.

#Getting started with Astro

To get started with Astro run the following:

Terminal window
npm create astro@latest

If you prefer you can install Astro manually.

With an Astro site created, you can commit the changes, push to your remote repository and deploy to Netlify.

#Deploy with Netlify

Creating a new Astro site on Netlify is simple. Once you’ve logged in, you’ll be taken to https://app.netlify.com. Click “Add new site” to add a new site, follow the on-screen steps and all the default settings will be completed for you.

For more information about deploying, you can read the Get Started with Netlify guide.

It’s a good idea to deploy now as you’ll need to add some environment variables to your Netlify site configuration in a later step.

#Create a new project with the Sanity CLI

If you’re new to Sanity you’ll need to sign up and create an account before moving to the next step: Sign up to Sanity.

To create a new Sanity project run the following command in your terminal.

Terminal window
npx sanity init --bare

Select Create new project at the prompt. You’ll then be asked to give your project and name and finally, enter Y to use the default dataset configuration.

You should then see the following output in your terminal.

Success! Below are your project details:
Project ID: {YOUR-PROJECT-ID}
Dataset: production

If you now head over to sanity.io/manage you’ll see your new project. Within the project’s settings you’ll find the same projectId and the dataset name as mentioned above.

You’ll need both of these in the next step.

Astro project detail screen

#Adding environment variables using the Netlify CLI

To help prevent sensitive variables from leaking into public repositories, you can use the Netlify CLI to automatically add environment variables to both your local, and production environments without needing to create a .env file in your project.

If you already have the Netlify CLI installed, run the following in your terminal to link your local development environment with your deployed Netlify site.

Terminal window
netlify link

A new browser window will open and you’ll be asked to authorize access.

From the prompts in the CLI select > Enter a site id, then head back over to your Netlify site configuration to find your site’s id and enter it in the CLI.

Netlify Site ID in the Netlify UI

After your local environment is linked, run the following in your terminal to add both the Sanity projectId and dataset.

Terminal window
netlify env:set PUBLIC_SANITY_STUDIO_PROJECT_ID {YOUR-PROJECT-ID}
netlify env:set PUBLIC_SANITY_STUDIO_DATASET production

Your local environment variables can now be made safely available to your local development environment by running it via netlify dev

Tip: Control over your local dev

While developing, instead of running npm run dev and accessing your Astro site on the default localhost port of 4321, you can run netlify dev and access your Astro site on 8888.

When you run netlify dev you’ll be prompted by the Netlify CLI to select a dev server to start, select the following.

Terminal window
[Astro] 'npm run dev'

#Adding Sanity Studio to Astro

To use Sanity with Astro there are two configurations. One is Sanity’s config, the other is the Sanity integration config which lives in your astro.config.mjs, we’ll cover both in the following steps.

#Embedding Sanity Studio

Embedding Sanity Studio within your project means the config and schema will live alongside your site code and you’ll be able to access the interface via an Astro page route.

In this guide, Sanity Studio will be available at /admin but you don’t need to create this page yourself.

You can read more about Embedding Sanity Studio in the docs.

#Install Sanity

Install the Sanity package.

Terminal window
npm install sanity@latest

#Create Sanity Config

Create a file at the root of your project and name it sanity.config.ts|js and add the following code.

// ./sanity.config.ts|js
import { defineConfig } from "sanity";
import { structureTool } from "sanity/structure";
export default defineConfig({
title: "Netlify Astro Sanity",
projectId: import.meta.env.PUBLIC_SANITY_STUDIO_PROJECT_ID,
dataset: import.meta.env.PUBLIC_SANITY_STUDIO_DATASET,
plugins: [structureTool()],
schema: {
types: [
{
type: "document",
name: "post",
title: "Post",
fields: [
{
name: "title",
title: "Title",
type: "string",
},
{
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "title",
maxLength: 96,
},
},
],
},
],
},
});

#Sanity Config Explained

The title is the name of your project and is displayed in the Sanity Studio UI. The projectId is the id of your Sanity project and the dataset is the name of the data variation stored within Sanity’s content lake.

Plugins are where you can add additional tools to Sanity Studio. The structure tool is what controls the UI in Sanity Studio.

You can read more about the Structure Tool in the Sanity docs.

Schema is how you define the fields for each content type in your content model. In this example we’re creating a simple document named post, and defining two fields, one for the title which is of type string, and one for the slug which is of type slug.

You can read more about Schema types in the Sanity docs.

#Sanity Astro Integration

In order to run Sanity Studio as part of your Astro site, and to query data from Sanity we’ll install and use the official sanity/astro integration. The integration will automatically configure a route within your site where the Sanity Studio React SPA will be available to view.

A note about React

Whilst it’s common in Astro sites to add frameworks, in this guide you’re only adding React for Sanity Studio. Your Astro site will still ship zero JavaScript to the client

Run the following in your terminal which will install the required dependencies.

Terminal window
npm install @sanity/astro @astrojs/react

In the next step we’ll configure the Astro Sanity integration which will require access to environment variables from your astro.config.mjs. To enable this you’ll need to install Vite.

Terminal window
npm install vite

#Astro Sanity integration configuration

As before with the sanity.config.js, you’ll need to add the projectId and dataset to the integration config.

Since Sanity Studio is a React SPA running within your Astro site the output has to be set to hybrid which allows Astro’s compiler to include the required React client-side JavaScript on the /admin route.

Because of this you will need to add Astro’s export const prerender = true to any pages that you wish to render as static pages. We’ll cover this in a later step.

The studioBasePath is used by the integration to create a route from where Sanity Studio can be viewed. In this guide, Sanity Studio is available on /admin.

In this guide we’ll be building static pages with Astro. Setting useCdn to false means the integration will use Sanity’s live API which isn’t cached so your static pages will always get the freshest data.

// ./astro.config.mjs
import { defineConfig } from "astro/config";
import sanityIntegration from "@sanity/astro";
import react from "@astrojs/react";
import { loadEnv } from "vite";
const { PUBLIC_SANITY_STUDIO_PROJECT_ID, PUBLIC_SANITY_STUDIO_DATASET } = loadEnv( process.env.NODE_ENV, process.cwd(), '');
export default defineConfig({
output: hybrid
integrations: [
sanityIntegration({
projectId: PUBLIC_SANITY_STUDIO_PROJECT_ID,
dataset: PUBLIC_SANITY_STUDIO_DATASET,
useCdn: true,
studioBasePath: '/admin',
}),
react(),
],
});

#Add The Netlify Adapter

The final step before you can start the development server is to add Astro’s Netlify adapter.

The Netlify adapter adds a Netlify function to your project which enables server-side page rendering, in cases where your pages would benefit from being up-to-minute fresh with data.

Run the following in your terminal which will install the adapter.

Terminal window
npm install @astrojs/netlify

And finally, update your astro.config.mjs to import and use the Netlify adapter.

// ./astro.config.mjs
import { defineConfig } from "astro/config";
import sanityIntegration from "@sanity/astro";
import react from "@astrojs/react";
import { loadEnv } from "vite";
import netlify from "@astrojs/netlify";
const { PUBLIC_SANITY_STUDIO_PROJECT_ID, PUBLIC_SANITY_STUDIO_DATASET } = loadEnv(
process.env.NODE_ENV,
process.cwd(),
""
);
export default defineConfig({
output: "hybrid",
integrations: [
sanityIntegration({
projectId: PUBLIC_SANITY_STUDIO_PROJECT_ID,
dataset: PUBLIC_SANITY_STUDIO_DATASET,
useCdn: false,
studioBasePath: "/admin",
}),
react(),
],
adapter: netlify(),
});

If you start the Astro dev server and visit http://localhost:8888/admin you should be looking at Sanity Studio!

Take care of CORS

The first time you visit Sanity Studio locally you’ll be prompted to add your localhost URL to Sanity’s CORS config. Click the button which will add the URL to your Sanity project.

You can go ahead and add a couple of test posts as we’ll need those next.

Sanity CMS UI

#Netlify Redirect

Whilst with this setup you will be able to view Sanity Studio locally, you’ll need to add a redirect so Netlify knows not to throw a 404 because there’s no actual admin.astro file/page in the project. You can read more about avoiding 404’s in your docs: JavaScript SPAs.

Create a netlify.toml at the root of your project and add the following code.

[[redirects]]
from = "/admin*"
to = "/admin"
status = 301
force = true

Notice the * at the end of the from route. This is a splat route and will ensure any requests made to any route that include /admin are correctly redirected to Sanity Studio which will handle the requests like a standard React SPA.

Commit the changes and push to your remote repository.

After a successful build you’ll be able to visit Sanity Studio on your production URL at /admin.

Take care of CORS

Just like in your local environment, the first time you visit Sanity Studio in production you’ll be prompted to add your production URL to Sanity’s CORS config.

#Update Astro Type Definitions

In the following step you’ll be querying data using the sanity:client. To avoid TypeScript warnings add the following to your env.d.ts.

// ./env.d.ts
/// <reference types="astro/client" />
/// <reference types="@sanity/astro/module" />

#Content Querying with Sanity and Astro

If you haven’t already added some test posts, do that now as it’s time to query the data!

#Create a page to query data

Create a page somewhere in your site, in the sample repository we’ve added this to index.astro but you add this to any .astro page.

// src/pages/index.astro
---
export const prerender = false;
import { sanityClient } from "sanity:client";
const posts = await sanityClient.fetch(`*[_type == "post" && defined(slug)]`);
---
<html>
<body>
<h1>Blog</h1>
<ul>
{
posts.map((post) => {
console.log(post);
return (
<li><a href={"/posts/" + post.slug.current}>{post.title}</a></li>
);
})
}
</ul>
</body>
</html>

The page is set to prerender = false, which means it will be server-side rendered. When a server-side rendered page is visited by a user, a new request is made to fetch the blog post date from Sanity. The query itself uses Sanity’s special GROQ query language.

You can read more about GROQ in the Sanity docs.

#Astro Dynamic Routes

Now that you can query data, and can create a list of links, we’ll need to create the pages for those links.

Create a new directory under src/pages named posts, and within the posts directory, create a new file named [slug].astro.

// src/pages/posts/[slug].astro
---
export const prerender = true;
import { sanityClient } from 'sanity:client';
export async function getStaticPaths() {
const posts = await sanityClient.fetch(`*[_type == "post" && defined(slug.current)]`);
return posts.map((post) => {
return {
params: {
slug: post.slug.current,
},
};
});
}
const { slug } = Astro.params;
const { title } = await sanityClient.fetch(`*[_type == "post" && slug.current == $slug][0]`, { slug });
---
<html>
<body>
<a href="/">Back</a>
<h1>{title}</h1>
</body>
</html>

This page is set to prerender = true, and will be statically rendered. It will only ever show updated Sanity data, or make a request to the Sanity API when the site/page is rebuilt. This is achieved using Netlify and Sanity’s build/webhooks which we’ll cover next.

If you’d prefer to have this page server-side rendered you can set prerender to false.

There are two Sanity queries required to find the correct data for each posts page.

#The First Sanity Query

The first Sanity query runs within Astro’s getStaticPaths function where we query all posts that have a slug. Each slug is then used to set the page’s params.slug value.

The slug is used by Astro in the dynamic file name. E.g, [slug].astro allowing Astro to create a page with the same name as the slug for each post returned by the Sanity query.

The slug value can be destructured from Astro.parms and used in the second Sanity query.

#The Second Sanity Query

The second Sanity query accepts two arguments.

  1. The first is the GROQ query that selects data of type “post” and checks if the value of the slug in the data is the same as the slug value destructured from Astro.params.
  2. The second argument is the slug value to use in this query.

In this example we’ve destructured the title value from the response.

Once you have data available you can return it in your HTML. In this example we display the title in an HTML h1 element.

You can read more about dynamic routing in the Astro docs.

#Build/Webhooks

To keep your static site up to date with changes made to the CMS, a build hook or webhook can be used to trigger a rebuild whenever changes in content occur. A rebuild will regenerate any pages that are statically rendered with the latest data from Sanity.

There are two sides to the build/webhooks:

  1. The Netlify Build Hook (which is called by)
  2. The Sanity Webhook

#Netlify Build Hook

The first is on the Netlify side. Navigate to Site configuration > Continuous deployment and scroll down until you see Build hooks.

Click “Add build hook”, and give your build hook a name, then select the branch to build from.

If you like, you can read a little more detail about Netlify Build Hooks

In this example we’ve called the build hook Sanity Build Hook and have selected to build from the main branch of our repository.

Netlify build hook config

When you click save you’ll be shown a URL which can be used on the Sanity side of the setup. Copy the URL and head back to Sanity Studio.

#Sanity Webhook

Navigate to API > Webhooks and fill out the fields. Give the webhook a name, then add the URL provided by Netlify. You’ll also need to select a dataset. In this example we’re using the production dataset.

Sanity webhook setup

With both sides now set up, go ahead and make a change to the content.

You should notice a new build with the message “Deploy triggered by hook” is now in your Netlify build queue.

Netlify deploy details

#Finished

And that’s it, you now have an Astro site with Sanity Studio deployed to Netlify complete with data queries and dynamic routes with a build hook to keep static data fresh. Congrats!

#Further Reading