Storyblok Raises $80M Series C - Read News

What’s the True Total Price of Enterprise CMS? Find out here.

Skip to main content

Render Storyblok Stories Dynamically in Astro

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

Having successfully integrated Storyblok into our Astro project in the last tutorial, let’s now take it one step further and add dynamically rendered pages.

Live demo:

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

Requirements

This tutorial is part 2 of the Ultimate Tutorial Series for Astro. We recommend that you follow the previous tutorial before starting this one.

Adding a header to our layout

Before creating additional pages in Storyblok, let’s, first of all, create a header with primary navigation for our website – because having multiple pages without being able to navigate between them doesn’t make much sense, does it? Since this won’t have a corresponding blok in our Storyblok space, we’ll create it in the src/components folder:

src/components/Header.astro
        
      <header class='w-full h-24 bg-[#f7f6fd]'>
  <div class='container h-full mx-auto flex items-center justify-between'>
    <a href='/'>
      <h1 class='text-[#50b0ae] text-3xl font-bold'>Storyblok Astro</h1>
    </a>
    <nav>
      <ul class='flex space-x-8 text-lg font-bold'>
        <li>
          <a href='/blog' class='hover:text-[#50b0ae]'>Blog</a>
        </li>
        <li>
          <a href='/about' class='hover:text-[#50b0ae]'>About</a>
        </li>
      </ul>
    </nav>
  </div>
</header>
    

Now we can implement this header component in our layout.

src/layouts/BaseLayout.astro
        
      +---
+import Header from '+../components/Header.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='mx-auto'>
+    <Header />
    <slot />
  </body>
</html>
    

And that’s it! If you take a look at your project now, you’ll see your header component:

Astro layout with header and navigation

Astro layout with header and navigation

Rendering Pages dynamically

Before actually creating any new pages in the Storyblok space, let’s take care of the required logic on the frontend side.

Fortunately, Astro makes this very convenient for us. All we have to do is to delete our src/pages/index.astro and replace it with a src/pages/[...slug].astro.

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

export async function getStaticPaths() {
  const storyblokApi = useStoryblokApi()

  const links = await storyblokApi.getAll('cdn/links', {
    version: 'draft',
  })
  return links
    .filter((link) => !link.is_folder)
    .map((link) => {
      return {
        params: {
          slug: link.slug === 'home' ? undefined : link.slug,
        },
      }
    })
}

const { slug } = Astro.params

const storyblokApi = useStoryblokApi()

const { data } = await storyblokApi.get(
  `cdn/stories/${slug === undefined ? 'home' : slug}`,
  {
    version: 'draft',
  }
)

const story = data.story
---

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

So, what’s happening here? We're using Astro's getStaticPaths function to create an array of paths that should be rendered by Astro. This step is necessary because, by default, Astro is a static site builder and needs to know which pages should be generated. To retrieve this list of pages, we're using Storyblok's links endpoint.

All fetched slugs will be returned using Astro.params. You might have noticed that of the slug retrieved from Storyblok equals home, we set it to undefined. This extra step makes an additional index.astro redundant, resulting in cleaner and easier-to-maintain code.

Hereafter, we can destructure the slug from Astro.params and fetch the corresponding story from Storyblok. If the slug equals undefined, we want our Home story to be displayed.

Hint:

This approach is compatible with both Astro's static site generation and server-side rendering mode. When SSR is enabled, the getStaticPaths function will be ignored and the slug will match the requested URL. Learn more about Astro's server-side rendering mode here.

Adding Pages in Storyblok

With our logic being complete, we can now add our Blog and About pages in our Storyblok space! To do that, simply go to Content {1}, Create new {2}, and then Choose Story {3}.

Adding a story
1
2
3

Adding a story

Now you can provide a name {1} – the slug will be filled out automatically for you. Let’s start with the About page.

Configuring a new story
1

Configuring a new story

Once the page is created, you can add nestable bloks to it. Simply click on the Plus Icon {1} and add a Teaser component {2}.

Inserting a new block
1
2

Inserting a new block

Now you can enter any headline you want for your newly created About page:

New block rendering correctly on the About page

New block rendering correctly on the About page

Try to repeat the same process on your own for the Blog page.

Wrapping Up

Congratulations! You have created your first pages in Storyblok and they are rendered in your Astro project dynamically – how cool is that!?

Next Part:

Continue reading and find out How to create Dynamic Menus in Storyblok and Astro. Not interested in dynamic menus? Feel free to skip this part and learn How to Create Custom Components in Storyblok and Astro.

Author

Manuel Schröder

Manuel Schröder

A former International Relations graduate, Manuel ultimately pursued a career in web development, working as a frontend engineer. His favorite technologies, other than Storyblok, include Vue, Astro, and Tailwind. These days, Manuel coordinates and oversees Storyblok's technical documentation, combining his technical expertise with his passion for writing and communication.