raj
Build your blog with Astro and Sanity

Build your blog with Astro and Sanity

Scroll 👇

Project Structure: Monorepo Setup

This site is also built using Astro(as a frontend) and Sanity(for creating blog). So i'll try to make this ASAP for you to understand.

The article provides a step-by-step guide on building a blog using Astro and Sanity. Astro is a framework for building websites, while Sanity Studio is a headless CMS. A headless CMS is a content management system (CMS) that allows you to manage your website's content without having to worry about the presentation layer. This means that you can create and edit your content in Sanity Studio, and then use Astro to style and display it on your website.

Set these up so that we have a studio folder and frontend folder inside a single my-blog folder:

~/my-blog 
├── studio
├── frontend

1. Setting up Sanity Studio using preconfigured blog setup from Sanity

To do this, open ~/my-blog/studio inside a terminal and use this command

npm create sanity@latest

For initial setup, log in with your sanity credentials. Ensure you're registered with Sanity beforehand. Answer prompted questions (see code block below). To skip setup, use ctrl + c. Customize preferences as needed for a straightforward process.
Here is what I chose.

This should create a basic blog template with post, category, and author schema. use following command to see it up and running.

yarn dev

or

npm run dev

Visit your localhost, log in with your provider, and access the Sanity dashboard to add a blog post.

2. Add a blog

Add a static blog to test from fronted

3. Install Astro and get it running

Once you have created a Sanity Studio project and added some content, you can start setting up the Astro frontend. To do this, you will need to create a new Astro project. This can be done by running the following command in your terminal: Navigate to ~/my-blog/frontend and use the following command.

npm create astro@latest

It will ask you this question.

Where should we create your new project?

In our case, you should use .

but you can use ./your-folder. This creates "your-folder" folder and sets up astro inside it. Since we already have a folder for our frontend we can use "." representing current folder.

After this you can run

npm run dev

and this will run your project in development environment, accessible at localhost:3000 in your browser.
Inside your code base, you'll find the index page at src/pages/index.astro. Modify the title and h1 element as needed, for example, change it to "My blog."

The file begins with ---, which serves as "code fences" in Astro, similar to Yaml in other tools. You'll use this feature later;
Inside src folder you'll find "pages" folder. Which should contain all of our pages. We already have an index.astro file inside pages folder which serves as home page.

4. Integrate Sanity in Astro frontend

Integrate your Sanity content into your Astro blog by installing the astro-sanity package.

npx astro add @sanity/astro

This will update astro.config.mjs to import sanity from @sanity/astro and add it as an integration, so that it appears like this:

import { defineConfig } from 'astro/config';
import sanity from "@sanity/astro";

export default defineConfig({
  integrations: [sanity()]
});

To make it function, pass your configuration as an object and instead of using sanity we can destructure sanity integration and use it. Copy paste below code in astro.config.mjs

import { defineConfig } from "astro/config";
import { sanityIntegration } from "@sanity/astro";

export default defineConfig({
  integrations: [
    sanityIntegration({
      projectId: "0cu1w3t5",
      dataset: "production",
      apiVersion: "2023-02-08",
      useCdn: false,
    }),
  ],
});

Copy your project ID and dataset name from sanity.config.ts located in the studio folder. and then add this line on "src/env.d.ts" to support types(Not necessary if using javascript).

/// <reference types="@sanity/astro/module" />

5. List all blogs on Astro frontend

On your home page "src/pages/index.astro". fetch all the posts using sanityClient given by sanity:client.

---
import { sanityClient } from 'sanity:client';
import Layout from '../layouts/Layout.astro';

const posts = await sanityClient.fetch(`*[_type == "post"]`);
---

<Layout title="Welcome to Astro.">
	<main>
		{posts && <ul>
			{posts.map((post) => (
				<li><a href={`post/${post.slug.current}`}>{post.title}</a></li>
			))}
		</ul>}
	</main>
</Layout>

Astro fetches data at build time, allowing us to loop through the posts in our component's HTML with the fetch in place.

Now that we have list of links. By clicking them we can navigate to blog page. But it will show 404 not found. Because we haven't created any page to show blog yet.

6. Create a blog page

Manually creating post routes is cumbersome for growing blogs. Luckily, Astro supports dynamic routing. Create a single route (e.g., [slug].astro) to handle all posts with unique slugs in their URLs.

So if we want our posts to be on /post/slug, we need to create a folder called post, which contains a file named [slug].astro.

In that file, export a function called getStaticPaths that returns an array of objects. Each object should include at least the slug parameter. To start, use this as the content of your [slug].astro file:

---
export function getStaticPaths() {
  return [
    {params: {slug: 'blog-1'}},
    {params: {slug: 'addition-blog'}},
    {params: {slug: 'unknown-blog'}},
  ];
}

const { slug } = Astro.params;
---

<h1>{slug} post</h1>

This code sets up data for a route, making it accessible via Astro.params. The framework automates this process, allowing you to use the slug in your template. For example, with 'blog-1', 'additional-blog', and 'unknown-blog', Astro creates files in the production build with corresponding headings. Access them locally at localhost:3000/post/blog-1 for a preview.

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

7. Show dynamic content on blog page.

Just like we fetch all posts on homepage. We will again use sanityClient to fetch a posts inside our getStaticPaths to create routes to all our post slugs and return slugs to Astro.params and post data to Astro.props.

Inside post/[slug].astro

---
import { sanityClient } from 'sanity:client';

export async function getStaticPaths() {
  const posts = await sanityClient.fetch(`*[_type == "post"]`);

  return posts.map((post) => {
    return {
      params: { slug: post.slug.current },
      props: { post }
    };
  });
}

const { slug } = Astro.params;
const { post } = Astro.props;
---

<h1>{slug}</h1>
<p>{JSON.stringify(post, null, 2)}</p>

This will show dynamic post content in json stringified format. And we don't want to show users in this way either. So in the next step we'll use image builder from "@sanity/image-url" package for getting images url posted on sanity studio and "astro-portabletext" package for formatting rich text in readable way.

7. Render rich text and images in proper way.

Let's install those 2 packages

npm i @sanity/image-url astro-portabletext

and create a utility function urlForImage which return a url in string format that we can give to src attribute in img tag. Inside src folder create "utils" or "helpers" folder. I's like to name it as "helpers". Inside helpers let's create a file urlForImage.ts:

// /my-blog/frontend/src/helpers/urlForImage.ts

import imageUrlBuilder from "@sanity/image-url";
import type { SanityImageSource } from "@sanity/image-url/lib/types/types";
import { sanityClient } from "sanity:client";

export const imageBuilder = imageUrlBuilder(sanityClient);

export function urlForImage(source: SanityImageSource) {
  return imageBuilder.image(source);
}

and now let's handle rich text.

For convenience, create an Astro component to render Portable Text. create a 'components' folder next to 'sanity' and 'pages', then create a file named PortableText.astro within it. Now that we have a way to show rich text we'll update our [slug].astro to show out post body.
Change your [slug].astro to

---
// src/pages/post/[slug].astro
import PortableText from '@/components/PortableText.astro';
import { sanityClient } from 'sanity:client';

export async function getStaticPaths() {
  const posts = await sanityClient.fetch(`*[_type == "post"]`);

  return posts.map((post) => {
    return {
      params: { slug: post.slug.current },
      props: { post }
    };
  });
}

const { slug } = Astro.params;
const { post } = Astro.props;
---

<h1>{slug}</h1>
<PortableText portableText={post.body} />

Inside my studio. I have this content Hello World and an image and some text.

but when i navigate to that post i can see rich text content but not image

Let's fix out PortableText.astro component. We actually need a PortableAstroImage.astro component to render a image inside rich text content. So let's create PortableAstroImage.astro component.

---
// src/components/PortableAstroImage.astro
import { urlForImage } from "../helpers/sanity";
const { asset, alt } = Astro.props.node;

const url = urlForImage(asset).url();
const webpUrl = urlForImage(asset).format("webp").url();
---

<picture>
  <source srcset={webpUrl} type="image/webp" />
  <img class="responsive__img" src={url} alt={alt} />
</picture>

This component is used in our PortableText.astro component. Update your PortableText.astro component to

---
// src/components/PortableText.astro
import { PortableText as PortableTextInternal } from "astro-portabletext";
import PortableTextImage from "./portable-text-image.astro"
const { portableText } = Astro.props;

const components = {
  type: {
    image: PortableTextImage,
  },
};
---

<PortableTextInternal value={portableText} components={components} />

This should fix your problem. Update UI as you like using css or tailwind css.

8. Next Steps

Achievement Unlocked: Your Astro site is now set up for showcasing blog content, managed seamlessly with Sanity Studio. Utilizing Astro's dynamic routes, static files are generated for each blog post during the build, ensuring readers access the latest version from your last build process.

For deployment, use 'npx sanity deploy' or'yarn deploy' for the studio and choose any hosting service like Netlify or Vercel for your Astro website. Jumpstart with the example project on GitHub.

Questions? Reach out on Linkedin