Developer guides

Build your blog with Astro and Sanity

A complete guide to setting up your blog using Astro and Sanity

Astro is a modern web framework that lets you build fast, content-focused websites. Combined with Sanity's flexible content platform, you can create a blog with powerful content management and real-time editing capabilities.

In this guide, we'll dive deeper into what you'll need to know in order to make a blog with Astro and Sanity. You'll learn how to:

This guide won't add styling to the markup, we'll leave that up to you. That said, it's often easier to develop the design when the basic markup and content are in place.

Prerequisites

This guide uses TypeScript for code examples, but you can adapt them to JavaScript if preferred. You don't need prior experience with Sanity or Astro.

Before starting, make sure you have:

  • Node.js 22.12 or later. (link)
  • A code editor.
  • Basic familiarity with TypeScript (optional).
  • This guide uses Astro v6 and Sanity v6.16. We recommend following along with these major versions.

Initialize a new Astro project

To create a new Astro project, run the following command:

Follow the instructions. When asked How would you like to start your new project? select A basic, minimal starter. You don't need to use TypeScript, but the examples in this guide use it.

Add dependencies

To add Sanity to your Astro project, install the official Sanity integration for Astro:

The command should add the Sanity and React configuration to your astro.config.mjs file. This is where you'll tell Astro what your Sanity project ID is, as well as the name of your dataset (most likely production).

The @astrojs/react dependency is needed to embed the Studio on a route.

Note: If you plan to add server-rendered pages or use the Visual Editing features mentioned later in this guide, you’ll also need to add a server adapter. For this example, we’ll use Node.

Then update the astro.config.mjs to include it.

Note for static-site users

To add TypeScript support, create a file /src/env.d.ts and add the types for the Astro module:

You may need to restart your TypeScript server for this file to be recognized.

Initialize a new Sanity project

To initialize your Sanity project and configure environment variables, run:

Follow the instructions from the CLI, and don't worry about messing up, with Sanity, you can make as many projects as you want. You can always go to sanity.io/manage to find information about your projects.

When the init command completes, the Sanity CLI writes two new environment variables to your .env file: PUBLIC_SANITY_PROJECT_ID and PUBLIC_SANITY_DATASET. These variables are prefixed with PUBLIC_ because they're not considered secrets.

Sanity client configuration

Astro has a unique limitation where you can't use variables from .env files directly in your astro.config.mjs file. Because your project ID and dataset name aren't considered sensitive you can directly copy + paste their values from your .env file. Astro's guide to environment variables in the config file explains how to read the .env file instead.

Update the sanity integration in your astro.config.mjs file to include the information needed by the Sanity client.

Embedding Sanity Studio

Sanity Studio is where you can edit and manage your content. It's a Single Page Application that's easy to configure and that can be customized in a lot of ways. It's up to you to keep the Studio in a separate repository, in a separate folder (as a monorepo), or embed it into your Astro website.

For the sake of simplicity, this guide will show you how to embed the Studio on a dedicated route (remember /wp-admin?).

Update astro.config.mjs to add a Studio at yoursite.com/studio

You must also add a configuration file for Sanity Studio in the project root. Create a new file called sanity.config.ts and add the following, note that we're able to use environment variables here:

Start the Astro local development server, you should be able to visit the Studio at http://localhost:4321/studio. The first time you load this URL, you'll be asked to add the URL to your project's CORS Origins. This is to enable authenticated requests from the browser to the Sanity APIs. Follow the instructions and reload the Studio route once you've added the setting.

Your project folder should now look like this:

.
├── public/
│   └── favicon.svg
├── src/
│   ├── assets/
│   │   ├── astro.svg
│   │   └── background.svg
│   ├── components/
│   │   └── Welcome.astro
│   ├── layouts/
│   │   └── Layout.astro
│   ├── pages/
│   │   └── index.astro
│   └── env.d.ts
├── .env
├── .gitignore
├── astro.config.mjs
├── package-lock.json
├── package.json
├── README.md
├── sanity.config.ts
└── tsconfig.json

Define the studio schema

Sanity is different from most headless CMSes. Content Lake, where your content is stored, is a schema-less backend that lets you store any JSON document and makes it instantly queryable with GROQ. Sanity Studio is a decoupled application that enables you to define a schema using JavaScript objects. The Studio uses the schema to build an editor interface where you can collaborate on content in real time.

This guide isn't going to cover schema creation in-depth, for now we'll copy and paste some starting schema definitions.

Create a new directory inside the src directory, called sanity with a directory schemaTypes inside of it. Create the following files inside /src/sanity/schemaTypes :

Create a file index.ts inside /src/sanity/schemaTypes

Update your sanity.config.ts file to include the new schema:

To recap: you created 3 document types - author, category, and post; as well as a reusable array type blockContent for editing Portable Text. If you refresh your Studio at http://localhost:4321/studio you should see the 3 document types listed, and the blockContent array will be visible when creating a post in the next step.

Create some example content

Create a post titled “Hello world” inside your Studio. At the slug field, press Generate to make a slug. Then press Publish – this makes the content publicly available via the API.

Loading...
What's a better way to get started with your blog than creating some Hello World content?

With the content created, the next step is to return to your Astro site and set it up to display your content.

Set up a blog post route in Astro

When you selected A basic, minimal starter template while creating this project, your Astro site was generated with only one route: an index page. To surface posts on our site, you'll want to create routes for each post. In Astro, routes exist as files on the file system in src/pages and are picked up by Astro as routes.

You could manually create routes for each post, but your posts are dynamic: when everything's up and running, you probably want to publish new content without pushing code. Astro, like most web frameworks, offers dynamic routing to make your life easier: you can create one route to catch them all using parameters.

In your blog's schema, every post has a slug, the unique bit of the URL (for example, “hello-world” for your “Hello world” post). To use a slug parameter in the route, you must wrap the filename in brackets. So, if you want your posts route to be /post/slug, you need to create a folder called post, which contains a file named [slug].astro.

In [slug].astro, you need to export a function called getStaticPaths that returns an array of objects. In our case, at the minimum, each object needs to include slug in its params. To get started, use this as the contents of your [slug].astro file:

This code sets up the data in this route within the code fences (---). Data returned from getStaticPaths is available in the Astro.params variable. This is a bit of magic the framework does for you. Now you can use the slug in your template. In this case, it results in a heading that contains whatever the slug is.

With the example above, Astro will generate three files, 'hello-world', 'my-favorite-things', and 'summertime' in the production build, with a heading that includes the slug. You can now browse to these on your local server. For instance, localhost:4321/post/summertime will display the heading “A post about summertime”.

Loading...
We can use 'slug' in our content

Of course, you want to display more than just the slugs, and you don't want to hardcode the slugs in this file. Let's get your data from Sanity and dynamically populate your post routes with your content.

Integrate your blog posts from Sanity in Astro

Create a new directory at ./src/sanity called lib and add a new file load-query.ts:

Head back to your [slug].astro file, import the loadQuery function, and use it to fetch your posts' slugs, like this:

Within the code fences, we export that same getStaticPaths function as before, but we've made it asynchronous so that we can wait for the data before returning the posts. With the loadQuery function, we fetch the posts using the Sanity client's fetch method (note: this is Sanity's fetch, not the Fetch API).

The argument we're passing into this fetch function, if you've not seen this syntax before, is a GROQ query.

Now you need to fetch the right blog post given a certain slug. Update [slug].astro with the following:

You should now be able to see the title of your "Hello World" post under /post/hello-world.

Loading...
If you have a post titled “Hello world” with “hello-world” as the slug, you should be able to find it in localhost:4321/post/hello-world.

Render images and block content

Now that you've seen how to display the title, continue to add the other bits of our posts: block content and images.

Background

With Sanity, your blog posts are part of your content model. They can be set up to be whatever you want, but we've used some boilerplate blog post schemas. Your post's title is a string, the published date is saved as a datetime and so on. Sanity has specific tooling for images and block content, so we'll add those first.

Images

When you use the image field type to allow users to upload images in your Studio, the images are uploaded to Sanity's CDN (the Asset Pipeline). It's set up so you can request them however you need them: in specific dimensions, image formats, or crops, just to name a few image transformations. The way this works is that the image is represented as an ID in your data structure. You can then use this ID to construct image URLs.

Use the image URL builder from the @sanity/image-url package for this. First install the dependency:

Create a new file inside ./src/sanity/lib called url-for-image.ts:

As you set up block content in the next step you'll use this function to performantly render your images.

Block content and rich text

The blog template saves your blog content in an array field of the block type. This will give you block content with rich text, which Sanity saves in a structured format called Portable Text. From Portable Text, you can generate Markdown, HTML, PDFs, or whatever else you want. It's very flexible. In this guide, you'll convert your Portable Text content to Astro components with the astro-portabletext library:

If you're using TypeScript it may be helpful to include the types for Portable Text:

Then, for convenience, create an Astro component to render our Portable Text for us. Create a new file called PortableText.astro inside of ./src/components:

This will render our Portable Text blocks, but we haven't yet added a component to handle any custom blocks we added to the Portable Text field, like image.

Create a file called PortableTextImage.astro in the same components folder:

This component will pass the relevant node from the Portable Text content, and we use our urlForImage function to calculate the asset URLs to display. Now, you can register this component to be rendered when PortableText encounters an image block:

Update [slug].astro to use the PortableText component to render the post content:

This uses the PortableText component we just added renders any content you've added, including links, images, and headings. This is an example of what it could look like:

Loading...
Bold text, links, images: authored in one rich text field and rendered in one PortableText component

Enable the Presentation Tool

Live Visual Editing is made possible through Sanity's Presentation Tool. To enable the Presentation Tool, we'll follow the steps outlined in the documentation for the Astro integration. The Presentation Tool provides two key benefits:

  • Overlays: All content stored in Sanity has an overlay added that when clicked brings users directly to editing that content in the Studio
  • Live mode: Edits made in the Studio are reflected on the front-end to provide authors immediate feedback

Create a layout file with the VisualEditing component

Update the Layout.astro file inside the src/layouts directory with the following:

In Layout.astro you're importing the VisualEditing component, which enables overlays and live mode for the Presentation Tool. Note the visualEditingEnabled constant tied to an environment variable PUBLIC_SANITY_VISUAL_EDITING_ENABLED set to true. If you haven't already, update your .env file to include this variable. When you're ready to deploy your site you'll want to have this variable set to false in production, but have another environment that's a copy of production with this variable set to true.

Update your [slug].astro template to be wrapped in the new layout:

Update your index.astro template to be wrapped in the new layout:

Update settings in astro.config file

Update the Sanity integration settings in astro.config.mjs to include stega.studioUrl

Adding this to the configuration allows the overlays to link to the appropriate place.

Generate a viewer token

In Sanity, drafts are considered private and are not accessible without a token.

In the top right of the Studio click on your user avatar, and click Manage project.

Loading...
Select "manage project" from this drop down

In your manage dashboard, navigate to API and down to Tokens. Click Add API token, give it any name you wish, ensure it has Viewer permissions, and click Save.

Loading...
Navigate to the token settings in manage and create a viewer token

Add this token to your .env file with the name SANITY_API_READ_TOKEN.

Update loadQuery to work with the Presentation Tool

Update ./src/sanity/lib/load-query.ts to the following:

There are a few things going on here, you're:

  • Modifying the perspective setting in the client to use drafts when Visual Editing is enabled
  • Returning a resultSourceMap for the overlays to know where to link to
  • Passing the token to the client to view drafts and enable Stega encoding (which powers the overlays)

Add the Presentation Tool to the Studio

Update your sanity.config.ts file to include the Presentation Tool in the plugins

Note the previewUrl, set to location.origin due to the Studio being embedded in your existing Astro app. If your Studio and front-end were hosted at different URLs you would update this value to point to the hosted front-end.

If you navigate to http://localhost:4321/studio/presentation you should see the Presentation Tool, and if you put the path to your blog post (/post/hello-world) in the tool's address bar you should see your front-end with overlays that bring you directly to your blog post.

Add a document location resolver

The Document Locations Resolver API lets you define where data is being used in your application(s), and it also lets you quickly preview a document from the Structure Tool.

For example if you have an author document open, enabling locations puts a widget at the top of the document with links to all documents on the site where this author is linked to.

Loading...
Location resolver adds this widget on top of the document

Create a new location resolver

Create a new file in ./src/sanity/lib/ , called resolve.ts

Add the location resolver to the Studio

Update your sanity.config.ts file to include the location resolver (resolve) inside the presentationTool

Now each post document in your Studio should include a link to open it in the Presentation Tool.

Loading...
Document locations show on "post" type documents

Enable Vite overrides (if needed)

With the release of Astro v6, there are some in-progress compatibility updates. If you’re experiencing issues with visual editing, add the configuration below.

Several transitive dependencies of @sanity/visual-editing are CommonJS modules that Vite doesn't automatically pre-bundle. Without this configuration, the VisualEditing component fails to hydrate in the browser.

In development, @sanity/astro already pre-bundles some Sanity dependencies, but not the modules listed in this configuration. If a later version resolves these errors, you can remove the vite.optimizeDeps setting.

Demo of Visual Editing with Astro

Next steps

You now have an Astro site that displays your blog content and a Sanity Studio to manage it. It uses Astro's dynamic routes to render a page for each post. Because the final configuration sets output: "server" with the Node adapter, Astro renders each page when it's requested, so readers see content you publish without a rebuild.

To keep building, see these guides:

Feel free to ask us questions on Discord, or however else you might find us.

Was this page helpful?