Skip to content

Strapi & Astro

Strapi is an open-source, customizable, headless CMS.

This guide will build a wrapper function to connect Strapi with Astro.

To get started, you will need to have the following:

  1. An Astro project - If you don’t have an Astro project yet, our Installation guide will get you up and running in no time.
  2. A Strapi CMS server - You can set up a Strapi server on a local environment.

To add your Strapi URL to Astro, create a .env file in the root of your project (if one does not already exist) and add the following variable:

.env
STRAPI_URL="http://127.0.0.1:1337" // or use your IP address

Now, you can use this environment variable in your Astro project.

If you would like to have IntelliSense for your environment variable, you can create a env.d.ts file in the src/ directory and configure ImportMetaEnv like this:

src/env.d.ts
interface ImportMetaEnv {
readonly STRAPI_URL: string;
}

Your root directory should now include the new file(s):

  • Directorysrc/
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

Create a new file at lib/strapi.ts and add the following wrapper function to interact with the Strapi API:

lib/strapi.ts
interface Props {
endpoint: string;
query?: Record<string, string>;
wrappedByKey?: string;
wrappedByList?: boolean;
}
/**
* Fetches data from the Strapi API
* @param endpoint - The endpoint to fetch from
* @param query - The query parameters to add to the url
* @param wrappedByKey - The key to unwrap the response from
* @param wrappedByList - If the response is a list, unwrap it
* @returns
*/
export default async function fetchApi<T>({
endpoint,
query,
wrappedByKey,
wrappedByList,
}: Props): Promise<T> {
if (endpoint.startsWith('/')) {
endpoint = endpoint.slice(1);
}
const url = new URL(`${import.meta.env.STRAPI_URL}/api/${endpoint}`);
if (query) {
Object.entries(query).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
const res = await fetch(url.toString());
let data = await res.json();
if (wrappedByKey) {
data = data[wrappedByKey];
}
if (wrappedByList) {
data = data[0];
}
return data as T;
}

This function requires an object with the following properties:

  1. endpoint - The enpoint you are fetching.
  2. query - The query parameters to add to the end of URL
  3. wrappedByKey - The data key in the object that wraps your Response.
  4. wrappedByList - A parameter to “unwrap” the list returned by Strapi, and return only the first item.

Optional: Creating the Article interface

Section titled Optional: Creating the Article interface

If you are using TypeScript, create the following Article interface to correspond to the Strapi content types at src/interfaces/article.ts:

src/interfaces/article.ts
export default interface Article {
id: number;
attributes: {
title: string;
description: string;
content: string;
slug: string;
createdAt: string;
updatedAt: string;
publishedAt: string;
};
}
  • Directorysrc/
    • Directoryinterfaces/
      • article.ts
    • Directorylib/
      • strapi.ts
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json
  1. Update your home page src/pages/index.astro to display a list of blog posts, each with a description and a link to its own page.

  2. Import the wrapper function and the interface. Add the following API call to fetch your articles and return a list:

src/pages/index.astro
---
import fetchApi from '../lib/strapi';
import type Article from '../interfaces/article';
const articles = await fetchApi<Article[]>({
endpoint: 'articles', // the content type to fetch
wrappedByKey: 'data', // the key to unwrap the response
});
---

The API call requests data from http://localhost:1337/api/articles and returns articles: an array of json objects representing your data:

[
{
id: 1,
attributes: {
title: "What's inside a Black Hole",
description: 'Maybe the answer is in this article, or not...',
content: "Well, we don't know yet...",
slug: 'what-s-inside-a-black-hole',
createdAt: '2023-05-28T13:19:46.421Z',
updatedAt: '2023-05-28T13:19:46.421Z',
publishedAt: '2023-05-28T13:19:45.826Z'
}
},
// ...
]
  1. Using data from the articles array returned by the API, display your Strapi blog posts in a list. These posts will link to their own individual pages, which you will create in the next step.
src/pages/index.astro
---
import fetchApi from '../lib/strapi';
import type Article from '../interfaces/article';
const articles = await fetchApi<Article[]>({
endpoint: 'articles?populate=image',
wrappedByKey: 'data',
});
---
<!DOCTYPE html>
<html lang="en">
<head>
<title>Strapi & Astro</title>
</head>
<body>
<main>
<ul>
{
articles.map((article) => (
<a href={`/blog/${article.attributes.slug}/`}>
{article.attributes.title}
</a>
))
}
</ul>
</main>
</body>
</html>

Create the file src/pages/blog/[slug].astro to dynamically generate a page for each article.

  • Directorysrc/
    • Directoryinterfaces/
      • article.ts
    • Directorylib/
      • strapi.ts
    • Directorypages/
      • index.astro
      • Directoryblog/
        • [slug].astro
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

In Astro’s default static mode (SSG), use getStaticPaths() to fetch your list of articles from Strapi.

---
import fetchApi from '../../lib/strapi';
import type Article from '../../interfaces/article';
export async function getStaticPaths() {
const articles = await fetchApi<Article[]>({
endpoint: 'articles',
wrappedByKey: 'data',
});
return articles.map((article) => ({
params: { slug: article.attributes.slug },
props: article,
}));
}
type Props = Article;
const article = Astro.props;
---

Create the template for each page using the properties of each post object.

---
import fetchApi from '../../lib/strapi';
import type Article from '../../interfaces/article';
export async function getStaticPaths() {
const articles = await fetchApi<Article[]>({
endpoint: 'articles',
wrappedByKey: 'data',
});
return articles.map((article) => ({
params: { slug: article.attributes.slug },
props: article,
}));
}
type Props = Article;
const article = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<title>{article.attributes.title}</title>
</head>
<body>
<main>
<img src={import.meta.env.STRAPI_URL + article.attributes.image.data.attributes.url} />
<h1>{article.attributes.title}</h1>
<!-- Render plain text -->
<p>{article.attributes.content}</p>
<!-- Render markdown -->
<MyMarkdownComponent>
{article.attributes.content}
</MyMarkdownComponent>
<!-- Render html -->
<Fragment set:html={article.attributes.content} />
</main>
</body>
</html>

If you’ve opted into SSR mode with output: server or output: hybrid, generate your dynamic routes using the following code:

Create the src/pages/blog/[slug].astro file:

src/pages/blog/[slug].astro
---
import fetchApi from '../../../lib/strapi';
import type Article from '../../../interfaces/article';
const { slug } = Astro.params;
let article: Article;
try {
article = await fetchApi<Article>({
endpoint: 'articles',
wrappedByKey: 'data',
wrappedByList: true,
query: {
'filters[slug][$eq]': slug || '',
},
});
} catch (error) {
return Astro.redirect('/404');
}
---
<!DOCTYPE html>
<html lang="en">
<head>
<title>{article.attributes.title}</title>
</head>
<body>
<main>
<img src={import.meta.env.STRAPI_URL + article.attributes.image.data.attributes.url} />
<h1>{article.attributes.title}</h1>
<!-- Render plain text -->
<p>{article.attributes.content}</p>
<!-- Render markdown -->
<MyMarkdownComponent>
{article.attributes.content}
</MyMarkdownComponent>
<!-- Render html -->
<Fragment set:html={article.attributes.content} />
</main>
</body>
</html>

This file will fetch and render the page data from Strapi that matches the dynamic slug parameter.

Since you are using a redirect to /404, create a 404 page in src/pages:

src/pages/404.astro
<html lang="en">
<head>
<title>Not found</title>
</head>
<body>
<p>Sorry, this page does not exist.</p>
<img src="https://http.cat/404" />
</body>
</html>

If the article is not found, the user will be redirected to this 404 page and be greeted by a lovely cat.

To deploy your website, visit our deployment guides and follow the instructions for your preferred hosting provider.

If your project is using Astro’s default static mode, you will need to set up a webhook to trigger a new build when your content changes. If you are using Netlify or Vercel as your hosting provider, you can use its webhook feature to trigger a new build from Strapi.

To set up a webhook in Netlify:

  1. Go to your site dashboard and click on Build & deploy.

  2. Under the Continuous Deployment tab, find the Build hooks section and click on Add build hook.

  3. Provide a name for your webhook and select the branch you want to trigger the build on. Click on Save and copy the generated URL.

To set up a webhook in Vercel:

  1. Go to your project dashboard and click on Settings.

  2. Under the Git tab, find the Deploy Hooks section.

  3. Provide a name for your webhook and the branch you want to trigger the build on. Click Add and copy the generated URL.

Follow the Strapi webhooks guide to create a webhook in your Strapi admin panel.

More CMS guides