/ astro-integrations / How to Use ButterCMS with Astro: Complete Guide
astro-integrations 4 min read

How to Use ButterCMS with Astro: Complete Guide

Step-by-step guide to integrating ButterCMS with your Astro website.

ButterCMS is an API-first content management system designed to be developer-friendly while keeping things simple. It comes with a built-in blog engine, pages, and reusable components, so you do not have to model everything from scratch. If you have ever set up a headless CMS and spent more time configuring content types than actually building features, ButterCMS aims to skip that phase entirely.

For Astro developers, ButterCMS provides a clean REST API with a JavaScript SDK. You fetch content at build time, generate static pages, and get the performance benefits of Astro with the content flexibility of a hosted CMS.

Prerequisites

  • Node.js 18+
  • An Astro project (npm create astro@latest)
  • A ButterCMS account (free tier available for testing, paid plans start at $83/mo)

Installation

Install the official ButterCMS JavaScript client:

npm install buttercms

Configuration

Create a ButterCMS client helper:

// src/lib/buttercms.ts
import Butter from "buttercms";

const butter = Butter(import.meta.env.BUTTERCMS_API_TOKEN);

export default butter;

Add your API token to .env:

BUTTERCMS_API_TOKEN=your_api_token_here

You can find your API token in the ButterCMS dashboard under Settings > API Token. The token is read-only, so it is safe to use in build-time code.

ButterCMS comes with a pre-built blog engine. Once you sign up, you already have blog post, category, tag, and author models ready to use. No custom type configuration needed for a standard blog.

Basic Usage

Fetch blog posts and render them on your Astro site:

---
// src/pages/blog/index.astro
import butter from "../../lib/buttercms";
import BaseLayout from "../../layouts/BaseLayout.astro";

const response = await butter.post.list({
  page: 1,
  page_size: 50,
});

const posts = response.data.data;
---

<BaseLayout title="Blog">
  <h1>Blog</h1>
  {posts.map((post) => (
    <article>
      <a href={`/blog/${post.slug}`}>
        {post.featured_image && (
          <img src={post.featured_image} alt={post.title} loading="lazy" />
        )}
        <h2>{post.title}</h2>
        <p>{post.summary}</p>
        <time>{new Date(post.published).toLocaleDateString()}</time>
      </a>
    </article>
  ))}
</BaseLayout>

Individual post pages with dynamic routes:

---
// src/pages/blog/[slug].astro
import butter from "../../lib/buttercms";
import BaseLayout from "../../layouts/BaseLayout.astro";

export async function getStaticPaths() {
  const response = await butter.post.list({ page: 1, page_size: 100 });
  const posts = response.data.data;

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

const { slug } = Astro.params;
const response = await butter.post.retrieve(slug);
const post = response.data.data;
---

<BaseLayout title={post.title}>
  <article>
    <h1>{post.title}</h1>
    <div class="meta">
      <span>By {post.author.first_name} {post.author.last_name}</span>
      <time>{new Date(post.published).toLocaleDateString()}</time>
    </div>
    {post.featured_image && (
      <img src={post.featured_image} alt={post.title} />
    )}
    <div class="content" set:html={post.body} />
    <div class="tags">
      {post.tags.map((tag) => (
        <a href={`/tag/${tag.slug}`}>{tag.name}</a>
      ))}
    </div>
  </article>
</BaseLayout>

ButterCMS also supports custom page types for things like landing pages:

// Fetch a page type
const response = await butter.page.list("landing_page", {
  page: 1,
  page_size: 10,
});

// Fetch a specific page
const page = await butter.page.retrieve("landing_page", "homepage");
const fields = page.data.data.fields;

Production Tips

  1. Use ButterCMS categories for content organization. Categories are built in and support nesting. Build category archive pages in Astro to improve SEO with topical content hubs that search engines value.

  • Set up webhooks for automatic deploys. ButterCMS supports webhooks that fire when content changes. Point these at your Vercel, Netlify, or Cloudflare Pages build hook URL so your site rebuilds whenever editors publish updates.

  • Paginate large collections. The API returns paginated results. For sites with many posts, loop through pages during getStaticPaths to fetch all content. Use page_size: 100 to minimize API calls.

  • Leverage the components API. ButterCMS has a "Components" feature for reusable content blocks like hero sections, CTAs, and testimonials. Define components in the dashboard, then map them to Astro components for a page-builder experience.

  • Cache API responses in CI. If your build pipeline runs frequently, cache ButterCMS responses to avoid hitting rate limits. Store the JSON responses locally during builds and only re-fetch when content actually changes.

  • Alternatives to Consider

    • Contentful if you need more advanced content modeling, localization, and a larger ecosystem of third-party integrations.
    • Ghost if you want a free self-hosted option with a similar built-in blog engine and a polished writing editor.
    • DatoCMS if you want a similar developer experience with built-in image optimization and better GraphQL support.

    Wrapping Up

    ButterCMS removes the content modeling overhead that comes with most headless CMS platforms. The pre-built blog engine, pages, and components system mean you spend less time in the CMS dashboard and more time building your Astro site. The API is clean, the SDK is simple, and the free tier lets you experiment without commitment. For teams that want a headless CMS that works out of the box without extensive configuration, ButterCMS delivers.