How to Use Strapi with Astro: Complete Guide
Step-by-step guide to integrating Strapi with your Astro website.
Strapi is an open-source headless CMS that gives you full control over your content API. Unlike hosted CMS platforms, you can self-host Strapi on your own server, customize the admin panel, and own your data completely. Pairing it with Astro means you get a flexible backend for content management and a fast static frontend for your visitors.
I have used this combo on a few projects now, and the developer experience is genuinely good. Strapi handles content modeling and the admin UI while Astro builds the pages at compile time. The result is a site that loads fast and costs almost nothing to host.
Prerequisites
- Node.js 18+
- An Astro project (
npm create astro@latest) - A running Strapi instance (local or cloud). You can spin one up with
npx create-strapi-app@latest my-project --quickstart - Strapi 4.x or 5.x
Installation
Strapi exposes a REST API by default, so you do not need a special SDK. However, installing a fetch wrapper makes things cleaner:
npm install qs
The qs package helps build query strings for Strapi's filtering and population parameters.
Configuration
Add your Strapi URL to a .env file:
STRAPI_URL=http://localhost:1337
STRAPI_API_TOKEN=your_api_token_here
Create the API token in your Strapi admin panel under Settings > API Tokens. Use a "Read-only" token for your frontend.
Now set up a helper to fetch from Strapi:
// src/lib/strapi.ts
import qs from "qs";
const STRAPI_URL = import.meta.env.STRAPI_URL || "http://localhost:1337";
const STRAPI_TOKEN = import.meta.env.STRAPI_API_TOKEN;
export async function fetchFromStrapi(endpoint: string, params?: Record<string, any>) {
const query = params ? `?${qs.stringify(params, { encodeValuesOnly: true })}` : "";
const res = await fetch(`${STRAPI_URL}/api/${endpoint}${query}`, {
headers: {
Authorization: `Bearer ${STRAPI_TOKEN}`,
},
});
if (!res.ok) {
throw new Error(`Strapi error: ${res.status} ${res.statusText}`);
}
const json = await res.json();
return json.data;
}
Basic Usage
Assuming you have a "Post" collection type in Strapi with fields like title, slug, content, and publishedAt, here is how to fetch and display posts:
---
// src/pages/blog/index.astro
import { fetchFromStrapi } from "../../lib/strapi";
import BaseLayout from "../../layouts/BaseLayout.astro";
const posts = await fetchFromStrapi("posts", {
sort: ["publishedAt:desc"],
populate: ["cover"],
fields: ["title", "slug", "publishedAt", "description"],
});
---
<BaseLayout title="Blog">
<h1>Blog</h1>
<ul>
{posts.map((post: any) => (
<li>
<a href={`/blog/${post.attributes.slug}`}>
<h2>{post.attributes.title}</h2>
<time>{new Date(post.attributes.publishedAt).toLocaleDateString()}</time>
</a>
</li>
))}
</ul>
</BaseLayout>
For individual post pages, use getStaticPaths:
---
// src/pages/blog/[slug].astro
import { fetchFromStrapi } from "../../lib/strapi";
import BaseLayout from "../../layouts/BaseLayout.astro";
export async function getStaticPaths() {
const posts = await fetchFromStrapi("posts", {
fields: ["slug"],
});
return posts.map((post: any) => ({
params: { slug: post.attributes.slug },
}));
}
const { slug } = Astro.params;
const posts = await fetchFromStrapi("posts", {
filters: { slug: { $eq: slug } },
populate: "*",
});
const post = posts[0];
---
<BaseLayout title={post.attributes.title}>
<article>
<h1>{post.attributes.title}</h1>
<div set:html={post.attributes.content} />
</article>
</BaseLayout>
Production Tips
Use the
populateparameter carefully. Strapi does not populate relations by default. Usepopulate: "*"for quick prototyping, but in production specify exactly which fields you need to keep response sizes small.
Set up webhooks for automatic rebuilds. In Strapi, go to Settings > Webhooks and add a URL that triggers your hosting platform's build hook. This way, every time content is published, your Astro site rebuilds automatically.
Cache your API responses. If you are using Astro in static mode, the data is fetched once at build time. For SSR mode, consider adding a caching layer (in-memory or Redis) to avoid hitting Strapi on every request.
Use Strapi's built-in image optimization. Strapi generates multiple image sizes automatically. Reference the formats object in your image responses to serve responsive images without a third-party service.
Run Strapi behind a reverse proxy in production. Put Nginx or Caddy in front of Strapi for SSL termination, rate limiting, and security headers. Never expose port 1337 directly to the internet.
Alternatives to Consider
- Contentful if you want a fully hosted solution with no server management. The tradeoff is less customization and vendor lock-in.
- Keystatic if your content team is comfortable with Git workflows. It stores everything in your repository, so there is no external service to manage.
- DatoCMS if you need built-in image optimization and localization out of the box with a cleaner API.
Wrapping Up
Strapi and Astro is one of the best self-hosted CMS combinations available. You get complete ownership of your content, a customizable admin panel, and the performance benefits of static site generation. The setup takes about 30 minutes, and from there you have a content pipeline that scales with your needs.
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.