Strapi
Strapi is an open-source headless CMS you self-host. Full control over your content schema, API, and data. Pairs perfectly with Astro. Fetch content via REST or GraphQL at build time.
Last updated: 2026-03-29
Setup
Spin up a Strapi instance locally or deploy to a cloud provider. Strapi gives you an admin panel where editors can create and manage content without touching code.
# Create a new Strapi project
npx create-strapi-app@latest my-cms --quickstart
# .env (in your Astro project)
STRAPI_URL=http://localhost:1337
STRAPI_TOKEN=your-api-tokenFetch Content
Strapi exposes a REST API by default. Create a helper to fetch content types from your Strapi instance. No SDK needed. Plain fetch works.
// src/lib/strapi.js
const STRAPI_URL = import.meta.env.STRAPI_URL;
const STRAPI_TOKEN = import.meta.env.STRAPI_TOKEN;
export async function fetchAPI(endpoint, params = {}) {
const query = new URLSearchParams(params).toString();
const res = await fetch(`${STRAPI_URL}/api/${endpoint}?${query}`, {
headers: {
Authorization: `Bearer ${STRAPI_TOKEN}`,
},
});
const json = await res.json();
return json.data;
}
export async function getPosts() {
return fetchAPI('posts', {
'populate': '*',
'sort': 'publishedAt:desc',
});
}
export async function getPostBySlug(slug) {
const posts = await fetchAPI('posts', {
'filters[slug][$eq]': slug,
'populate': '*',
});
return posts[0];
}Render in Astro
Call your Strapi helper in Astro frontmatter. Content is fetched at build time for static sites. Use webhooks to trigger rebuilds when editors publish new content.
---
// src/pages/blog/index.astro
import { getPosts } from '../../lib/strapi';
const posts = await getPosts();
---
<section class="grid gap-6">
{posts.map((post) => (
<a href={`/blog/${post.attributes.slug}`}>
<h2>{post.attributes.title}</h2>
<p>{post.attributes.excerpt}</p>
<time>{new Date(post.attributes.publishedAt).toLocaleDateString()}</time>
</a>
))}
</section>Best Practices
Strapi is flexible. Keep your content types simple and use relations sparingly.