Add a headless CMS to Astro in 3 easy steps
Tags: jamstack, astro, javascript, webdev
Published: 2022-09-20 14:48:52

[Astro](https://astro.build/) is a lightweight web framework capable of shipping highly performant websites with minimal (or non-existent) JavaScript bundles. In this guide, we’re going to create our content with Cosmic and retrieve it in our Astro project, allowing for quick and easy content creation and collaboration. The end result will be a clean and composable website that aces the lighthouse scores all across the board.

![Simple Astro Blog desktop lighthouse scores](https://imgix.cosmicjs.com/7347c300-382d-11ed-adfd-ddb1795c6ac6-astro-blog-desktop-lighthouse-min.png)

### TL;DR

Install the [Simple Astro Blog](https://www.cosmicjs.com/apps/simple-astro-blog) Cosmic template.

Download the Simple Astro Blog [source code](https://github.com/cosmicjs/simple-astro-blog).

## Step 1: Configure your Astro project

To create a new Astro project, run `npm create astro@latest`.  Astro provides a convenient [CLI tool](https://docs.astro.build/en/install/auto/) to set up some boilerplate code and get your project configured quickly. You can choose from a few different templates, but in our case, we’re keeping it simple with the “Just the Basics” option configuring a nice organized directory, a few Astro components to reference, and an Astro config file.

If you are using Cosmic in this guide, make sure you install the [Cosmic NPM package](https://www.npmjs.com/package/cosmicjs):

```bash
# npm
npm install cosmicjs
# pnpm
pnpm add cosmicjs
# yarn
yarn add cosmicjs
```

Next, we will create two items in our project directory. First, create a **lib** folder in the **src** folder, and then create a **cosmic.js** file. After you’ve created this file, create a **.env** in the root of your directory. Here, we will store our environment variables from the Cosmic Bucket we’re about to set up, and in our **cosmic.js** file we will import the environment variables and create our API calls to retrieve our data.

Once you’ve created these two items, put them aside for now, as we go on to creating our Content Model in Cosmic.

### Step 1.5 (Optional): Adding Astro Integrations

Astro provides some useful [integrations](https://docs.astro.build/en/guides/integrations-guide/), allowing you to quickly configure your favorite UI libraries, SSR adapters, and other tools like [Astro Image](https://docs.astro.build/en/guides/integrations-guide/image/) and [Tailwind CSS](https://tailwindcss.com/). For this guide, we encourage using both the [Astro Image](https://docs.astro.build/en/guides/integrations-guide/image/) and [Tailwind CSS](https://docs.astro.build/en/guides/integrations-guide/tailwind/) integrations as they will improve website performance and developer experience.

Using the Astro CLI, we can run the commands `npx astro add image` and `npx astro add tailwind`, and Astro will install each individual package and update the **astro.config.mjs** file to support the integration. 

## Step 2: Configure a Cosmic Bucket

### What is a headless CMS?

With a headless CMS, content is created using a dashboard and then delivered using an API. The content layer is separated from the presentation layer, making content flexible, reusable, and scalable. If you’d like to learn more about what a headless CMS is, we created [this concise article](https://www.cosmicjs.com/headless-cms) explaining it.

### Create an account

If you haven’t already, create a [Cosmic account](https://app.cosmicjs.com/signup). Then navigate to the dashboard to create a new project. We’re going to start from scratch here, but you choose from a wide variety of templates by selecting “Start with a template” and clicking “Select Template” on the desired template. Name your project and Bucket Environment, then click “Save Bucket.”

![Creating a new bucket in the Cosmic headless CMS dashboard](https://imgix.cosmicjs.com/f3417ce0-3440-11ed-984d-15ac5c458dc7-new-astro-bucket-min.png)

### Creating an Object

Let’s create a simple Object, click “Add Object Type” and then you will see that we can now setup our first Object. We’re going to call this Object “Posts”, which will contain multiple blog posts, so go ahead and select “Multiple” then under “Singular Name” type in “Post.”

Our default fields will contain the **Slug field** and the **Content field**, so you can leave these as they are.

### Content Model

The Content Model is where we design the structure of our content. We can use different [Metafields](https://docs.cosmicjs.com/api-reference/metafields) like strings, numbers, and even media (images). From this structure, we can craft our API calls to retrieve this specific data and render it onto our application.

Our Object will consist of:

- An Excerpt (text)
- A Cover Image (media)

![Designing a content model in the Cosmic headless CMS](https://imgix.cosmicjs.com/88e0a3f0-3443-11ed-984d-15ac5c458dc7-astro-bucket-posts-min.png)

Once you’ve created these Metafields, click “Save Object Type.”

Let’s create our first Post! Navigate to “Posts” in the sidebar and click “Add Post.” Fill in the **Title**, **Excerpt**, and **Content** with some sample text. Then, upload an image for the **Cover Image** and select “Publish.”

Now that we’ve got our first published Object, we’re ready to tie it all together and get this data into our Astro application.

## Step 3: Retrieve data from the Cosmic API

Before we leave the Cosmic dashboard, we need to grab a couple of things. On the sidebar, click on **Settings>API Acess.** To make this easier, I’m going to rename the **Bucket Slug** to something like **my-astro-blog** then click “Save Settings”**.**

Now we’re going to take the **Bucket Slug** and **Read Key** values and place them into our **.env** file in our Astro app.

```bash
# .env
PUBLIC_COSMIC_BUCKET_SLUG=my-astro-blog
PUBLIC_COSMIC_READ_KEY=<your_read_key_here>
```

Once set, let’s go into the **cosmic.js** file we created in Step 1. We will import the Cosmic module, instantiate an **api** variable and import our environment variables. Refer to [these docs](https://docs.astro.build/en/guides/environment-variables/) for setting environment variables in Astro.

```jsx
// src/lib/cosmic.js
import Cosmic from "cosmicjs";
const api = Cosmic();

const bucket = api.bucket({
  slug: import.meta.env.PUBLIC_COSMIC_BUCKET_SLUG,
  read_key: import.meta.env.PUBLIC_COSMIC_READ_KEY,
});
```

Now that we’re all set up, let’s write our API call to get the Post we made in our dashboard.

```bash
// src/lib/cosmic.js
export async function getAllPosts() {
  const data = await bucket.objects
    .find({
      type: "posts",
    })
    .props("title,slug,content,metadata")
  return data.objects;
}
```

Using Cosmic’s [chaining methods](https://docs.cosmicjs.com/examples/basic-queries), we can **find** the specific type of Object in our API, which is “posts” (the slug of the Object itself), and set the **props** to all of the parameters we set up when we created our Object earlier. In our case, we want to retrieve the **title**, **slug**, **content**, and the Cover Image, which lives inside of the **metadata.** 

Remember that the default parameters are accessible at a higher level than the metadata, and the metadata itself comes from the custom metafields you create within an Object. Grabbing the Cover Image would look like this: **metadata.cover_image.imgix_url**.

### Rendering the data

Now we can pull in the API call onto one of our Astro pages. By calling the `getAllPosts` function in the “helmet” of our Astro page or component, we can access all of the content we create in Cosmic. 

```jsx
// src/pages/index.astro
import { getAllPosts } from "../lib/cosmic"
const data = await getAllPosts()
```

To render out a list of the posts we make, we can create a minimal Card component like this:

```jsx
// src/components/Card.Astro
---
const { href, title, body } = Astro.props
---
<li class="bg-white rounded-md shadow-md hover:shadow-xl transition">
  <a href={`/blog/${href}`}>
    <div class="flex flex-col h-full gap-y-2 p-6">
      <h2 class="font-semibold text-xl pr-4 pt-2">
        {title}
      </h2>
      <p>
        {body}
      </p>
    </div>
  </a>
</li>
```

Note that this is an Astro component. We can pass props to it by defining a de-structured object to [Astro.props](https://docs.astro.build/en/core-concepts/astro-components/#component-props). This functionality is done within the dashes at the top of the component, or the “helmet.”

Going back to our index.astro page, let’s pass the data from Cosmic and render the card.

```
// src/pages/index.astro
---
import { getAllPosts } from "../lib/cosmic"
import Card from "../components/Card.astro";
const data = await getAllPosts()
---

<section>
    <ul class="grid md:grid-cols-2 gap-8">
      {
        data.map((post) => (
          <Card
            title={post.title}
            href={post.slug}
            body={post.metadata.excerpt}
          />
        ))
      }
    </ul>
  </section>
```

You’ll notice that we are passing the slug of our Cosmic post into the **href** prop. We can generate dynamic routes easily with Astro. We do this using [getStaticPaths](https://docs.astro.build/en/reference/api-reference/#getstaticpaths), which will generate dynamic page routes at build time.

```jsx
// src/pages/blolg/[slug].astro
---
import { getAllPosts } from "../../lib/cosmic";
import { Image } from "@astrojs/image/components";

export async function getStaticPaths() {
  const data = (await getAllPosts()) || [];

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

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

<article>
  <section class="border-b pb-8">
    <h1 class="text-4xl font-bold">{post.title}</h1>
    <div class="my-8"></div>
  </section>
  <Image
    src={post.metadata.cover_image.imgix_url}
    format="webp"
    width={1200}
    aspectRatio={16 / 9}
    quality={50}
    alt=""
    class={"rounded-md shadow-lg my-12"}
  />
  <p>{post.content}</p>
</article>
```

Using **getStaticPaths**, we are generating the path for each “Post” in Cosmic using it’s **slug**. This happens at build time and uses the data stored in **params**. We then pass the data for each “Post” through to the **props** and further set the { **post** } object to **Astro.props.** We can re-use our HTML template below the helmet for each blog post we render.

![Simple Astro Blog article page](https://imgix.cosmicjs.com/693d0ba0-3480-11ed-984d-15ac5c458dc7-astro-blog-page-min.png)

After running a mobile Lighthouse report for the application, we have achieved 100s all across the board with little effort.

![Simple Astro Blog mobile lighthouse scores](https://imgix.cosmicjs.com/73488650-382d-11ed-adfd-ddb1795c6ac6-astro-blog-mobile-lighthouse-min.png)

## **Conclusion**

We hope you found this guide helpful to learn how Astro provides ease of use in development, easy integration with a headless CMS, and the capability to achieve high-performing Lighthouse scores out of the box. For more info on Astro and headless CMS, you can read about it [here](https://www.cosmicjs.com/knowledge-base/astro-cms). You can check out the template used in this guide [here](https://www.cosmicjs.com/apps/simple-astro-blog). You can tune into our podcast, [Build Time](https://www.cosmicjs.com/blog/introducing-build-time-a-new-podcast-by-cosmic), where we cover topics like headless CMS, Next.js, React, and *many many* more.