Almost EVERYONE who tried headless systems said they saw benefits. Download the state of CMS now!

Storyblok now on AWS Marketplace: Read more

O’Reilly Report: Decoupled Applications and Composable Web Architectures - Download Now

Empower your teams & get a 582% ROI: See Storyblok's CMS in action

Skip to main content

Add a headless CMS to Astro in 5 minutes

In this tutorial, we’ll learn how to retrieve and display data from the Storyblok API in Astro in order to create a highly performant website with one of the most modern static site generators that exist.

Before moving on, if you’re unfamiliar with the concept of a Headless CMS or Storyblok in particular, you may consult the Storyblok Guide.

Live demo:

In a hurry? Check out the source code on GitHub and take a look at the live version on Netlify.

Section titled Environment Setup Environment Setup

Section titled Requirements Requirements

In order to follow this tutorial, please make sure you meet these requirements:

  • Basic understanding of HTML and JavaScript
  • Node.js LTS version installed
  • An account in the Storyblok App

Section titled Create an Astro project Create an Astro project

Let’s create a new Astro project by running the following command:

        
      npm create astro@latest
    

The wizard will guide us through the installation process. For this tutorial, we want to choose the following options:

  • Where should we create your new project? Choose your desired project folder.
  • How would you like to start your new project? Empty
  • Install dependencies? Yes
  • Do you plan to write TypeScript? No
  • Initialize a new git repository? Up to you!

After the installation has completed, you can go to the project folder and run npm run dev to start the development server.

Section titled Adding TailwindCSS Adding TailwindCSS

For the styling of our Astro website, we are going to employ Tailwind CSS. Luckily, adding Tailwind to Astro is a breeze.

All you have to run is the following command:

        
      npx astro add tailwindcss
    

And that's it!

Section titled Configuration of the space Configuration of the space

You can easily configure a new space by clicking Add Space {1} after having logged in to Storyblok.

Creating a new space in Storyblok
1

Creating a new space in Storyblok

Create a new space in the Storyblok app by choosing the Create space {1} option. Pick a name for it {2}. Optionally, you can choose between different server locations for your space {3} (if you choose the United States or China, please be mindful of the required API parameter explained hereinafter).

Creating a new space in Storyblok
1
2
3

Creating a new space in Storyblok

Shortly afterward, a Storyblok space with sample content has been created for you. Let’s open the Home story by first clicking on Content {1} and then on Home {2}:

Opening the Home story
1
2

Opening the Home story

Now you’ll see the default screen and the Visual Editor:

Visual Editor representing your Home story

Visual Editor representing your Home story

Section titled Enabling the Visual Editor Enabling the Visual Editor

In order to see your website in the Storyblok Visual Editor, the default environment URL has to be configured accordingly. To achieve that, simply go to Settings {1} > Visual Editor {2} and the Location {3} field to https://localhost:3000/:

Setting the location for the Visual Editor
1
2
3

Setting the location for the Visual Editor

warn:

Storyblok v2 requires that your website is served via HTTPS. You can follow these instructions to set up your dev server accordingly.

Having done that, you can now go back to the Home story. However, you’ll notice that you won’t see the welcome screen of your Astro project yet.

Just one more step to go: open the Entry configuration {1} and set the Real path to / {2}. Save the story and you’ll see the default screen of your Astro project in the Visual Editor.

Section titled Connecting Astro to Storyblok Connecting Astro to Storyblok

First of all, we’ll have to install @storyblok/astro, the official SDK to interact with the Storyblok API.

Let’s add it to our project by running:

        
      npm install @storyblok/astro
    

In order to access the data from your newly created space, you need to get your unique API token, which can be found in Settings {1} > Access tokens {2}. Here you can copy the Preview token {3} that's been generated for you.

1
2
3

You can store your preview access token in an .env file in your project root like this:

        
      STORYBLOK_TOKEN=YOUR_PREVIEW_TOKEN
    

In order to use environment variables in Astro, we quickly need to install Vite as a dependency:

        
      npm install vite
    

Now we can use the token and initialize the Storyblok integration in astro.config.mjs:

        
      import { defineConfig } from 'astro/config'
import storyblok from '@storyblok/astro'
import { loadEnv } from 'vite'
import tailwind from "@astrojs/tailwind"
import basicSsl from '@vitejs/plugin-basic-ssl'
const env = loadEnv("", process.cwd(), 'STORYBLOK')

export default defineConfig({
  integrations: [
    storyblok({
      accessToken: env.STORYBLOK_TOKEN,
    }),
    tailwind()
  ],
  vite: {
    plugins: [basicSsl()],
    server: {
      https: true,
    },
  },
})
    

Section titled Setting the correct region Setting the correct region

Depending on whether your space was created in the EU, the US, Australia, Canada, or China, you may need to set the region parameter of the API accordingly:

  • eu (default): For spaces created in the EU
  • us: For spaces created in the US
  • ap: For spaces created in Australia
  • ca: For spaces created in Canada
  • cn: For spaces created in China

Here's an example for a space created in the US:

        
      apiOptions: {
  region: "us",
},
    
WARN:

Note: For spaces created in any region other than the EU, the region parameter must be specified.

Section titled Creating a layout and an index page Creating a layout and an index page

Let’s use the useStoryblokApi function we just created to fetch the data of the Home story. Simply replace the existing src/pages/index.astro file with the following code:

src/pages/index.astro
        
      ---
import { useStoryblokApi } from '@storyblok/astro'
import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'
import BaseLayout from '../layouts/BaseLayout.astro'

const storyblokApi = useStoryblokApi()
const { data } = await storyblokApi.get('cdn/stories/home', {
  version: 'draft',
})

const story = data.story
---

<BaseLayout>
  <StoryblokComponent blok={story.content} />
</BaseLayout>
    

As you can see, a component called StoryblokComponent is imported from the SDK. This component is used to dynamically resolve all of the components that exist in our Storyblok space. Let’s take a closer look at this in the next step.

Moreover, we utilize a layout component. Let’s create it:

src/layouts/BaseLayout.astro
        
      <html lang='en'>
  <head>
    <meta charset='UTF-8' />
    <meta name='viewport' content='width=device-width' />
    <link rel='icon' type='image/x-icon' href='/favicon.ico' />
    <title>@storyblok/astro</title>
  </head>
  <body class='container mx-auto'>
    <slot />
  </body>
</html>
    
hint:

Learn more about Astro Layouts.

Section titled Rendering the Storyblok components Rendering the Storyblok components

When you create a new Storyblok space and choose to start from scratch, the following four components are created automatically for you:

  • page: Content type block
  • grid: Nestable block
  • feature: Nestable block
  • teaser: Nestable block

You can find all of these in the Block Library of your space.

hint:

You can learn more about content types and nestable components in our Structures of Content tutorial.

In order to provide the frontend counterparts for these components in our Astro project, we, first of all, need to register them properly in the Storyblok integration. Let's change astro.config.mjs accordingly:

        
      storyblok({
  accessToken: env.STORYBLOK_TOKEN,
+  components: {
+    page: 'storyblok/Page',
+    feature: 'storyblok/Feature',
+    grid: 'storyblok/Grid',
+    teaser: 'storyblok/Teaser',
+  },
})
    
HINT:

It's crucial that the keys in the components object match the technical names of the blocks defined in your Storyblok space.

As it is impossible to know in advance which nestable components or bloks will need to be rendered to display our story, StoryblokComponent.astro provided by @storyblok/astro loads the required components based on their technical name.

Having completed this step, we can now create the actual components and display the information that is being passed on in the blok property object.

src/storyblok/Page.astro
        
      ---
import { storyblokEditable } from '@storyblok/astro'
import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'

const { blok } = Astro.props
---

<main {...storyblokEditable(blok)}>
  {
    blok.body?.map((blok) => {
      return <StoryblokComponent blok={blok} />
    })
  }
</main>
    
src/storyblok/Grid.astro
        
      ---
import { storyblokEditable } from '@storyblok/astro'
import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'

const { blok } = Astro.props
---

<div {...storyblokEditable(blok)}>
  <div
    class='container mx-auto grid md:grid-cols-3 gap-12 my-12 place-items-center'
  >
    {
      blok.columns?.map((blok) => {
        return <StoryblokComponent blok={blok} />
      })
    }
  </div>
</div>

    

As you can see, both Page.astro and Grid.astro utilize StoryblokComponent.astro to render any nested blocks in the body or columns fields respectively.

src/storyblok/Teaser.astro
        
      ---
import { storyblokEditable } from '@storyblok/astro'

const { blok } = Astro.props
---

<div {...storyblokEditable(blok)}>
  <h3 class='py-32 text-6xl text-[#50b0ae] font-bold text-center'>
    {blok.headline}
  </h3>
</div>
    
src/storyblok/Feature.astro
        
      ---
import { storyblokEditable } from '@storyblok/astro'

const { blok } = Astro.props
---

<div
  {...storyblokEditable(blok)}
  class='w-full p-12 bg-[#f7f6fd] rounded-[5px] text-center'
>
  <h3 class='text-2xl text-[#1d243d] font-bold'>{blok.name}</h3>
</div>
    
hint:

Have you noticed the storyblokEditable function? It is required for the Storyblok JavaScript Bridge, allowing you to interact with your frontend components in the Visual Editor.

Finally, after having created all of these, you can launch your development server again by running npm run dev. Now, if you go to the Home story in your Storyblok space, you should see your Teaser, Grid, and Feature components being rendered:

Astro components being rendered correctly

Astro components being rendered correctly

Section titled Limitations of using the Storyblok Bridge with Astro Limitations of using the Storyblok Bridge with Astro

Since one of the core ideas of Astro is to ship less JavaScript, its native components are not reactive. Therefore, real-time editing in the Storyblok Visual Editor as you may know from using Storyblok with a framework like Next.js won’t be possible.

However, what we can do is enable the functionality that clicking on components immediately shows the related content fields. Additionally, we can trigger a reload of our Astro frontend whenever the Save button in Storyblok is used.

hint:

The Storyblok Bridge is enabled by default for you. However, you can choose to disable it using the bridge parameter. Learn more in the official SDK readme.

Section titled Wrapping Up Wrapping Up

Congratulations! You have successfully integrated your Astro project with Storyblok, resulting in a lightning-fast static site with a phenomenal editing experience.

Next part:

Continue reading and find out how to Render Storyblok Stories Dynamically in Astro.

Author

Manuel Schröder

Manuel Schröder

Manuel is a front-end engineer who loves crafting beautiful and fast websites. International Relations graduate turned WordPress expert, he ultimately developed a passion for the Jamstack. His favorite technologies include Vue, Astro, Tailwind, and Storyblok. These days, Manuel works as a Developer Relations Engineer at Storyblok.