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

Create Dynamic Menus in Storyblok and Astro

Try Storyblok

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

In this part of the tutorial series, we will make the menu in our header component dynamic, so that you can manage it directly from Storyblok.

Live demo:

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

Section titled Requirements Requirements

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


Section titled Setup in Storyblok Setup in Storyblok

First, we will have to create a new content type component wherein our menu entries can be stored. In order to do that, go to the Block Library {1} and create a New block {2}.

Creating a new block in the Block Library
1
2

Creating a new block in the Block Library

Enter the name config {1} and choose Content type block {2}.

Creating a content type block
1
2

Creating a content type block

Now you can create a new field with the name header_menu {1} and choose the field type Blocks {2}.

Creating a field for the header menu
1
2

Creating a field for the header menu

In this field, we would like to provide the possibility to add menu links as nested blocks. To accomplish that, let’s create another new block. This time it should be a Nested block {1} with the name menu_link {2}.

Creating a nested block
1
2

Creating a nested block

Now we can add a new field called link {1} in this newly created block and choose Link as the field type {2}.

Creating a link field
1
2

Creating a link field

Alright, our component schemas are almost done! Just one more step: to avoid that just any block could be nested in our header_menu, we want to make sure that only specific components can be inserted {1}. Now you can choose the menu_link block in the whitelist {2}.

Allowing only specific components to be inserted
1
2

Allowing only specific components to be inserted

With that out of the way, we can now go to the Content section of our Storyblok space. Here, we want to create a new story with the name Config {2}, using our recently created content type Config {3}.

Creating a new Config story
1
2

Creating a new Config story

If you open this newly created Config story in Storyblok it will throw an Error saying there is no Config component defined. We will learn more about how to create a component in the next part of this series. But for now, just do the following.

  1. Create a new file name src/storyblok/Config.astro and just add an empty div in this file.
  2. Register the component in the Storyblok integration in astro.config.mjs.
src/storyblok/Config.astro
        
      <div></div>
    
astro.config.mjs
        
      export default defineConfig({
  integrations: [
    storyblok({
      accessToken: env.STORYBLOK_TOKEN,
      components: {
        page: 'storyblok/Page',
+        config: 'storyblok/Config',
        feature: 'storyblok/Feature',
        grid: 'storyblok/Grid',
        teaser: 'storyblok/Teaser',
      },
    }),
    tailwind(),
  ],
})
    

You can now nest as many menu_link blocks in the header_menu field as you would like. For now, let’s add our Blog and About page.

Dynamic menu rendered correctly in Astro

Dynamic menu rendered correctly in Astro

Section titled Rendering the Menu in Astro Rendering the Menu in Astro

Having taken care of our setup in Storyblok, we can now work on implementing our dynamic menu in the frontend. To accomplish that, let’s update src/components/Header.astro with the following code:

components/Header.astro
        
      ---
import { useStoryblokApi } from '@storyblok/astro'
const storyblokApi = useStoryblokApi()
const { data } = await storyblokApi.get('cdn/stories/config', {
  version: 'draft',
  resolve_links: 'url',
})
const headerMenu = data?.story?.content?.header_menu
---

<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">
        {
          headerMenu.map((menu) => (
            <li>
              <a href={`/${menu.link.cached_url}`} class="hover:text-[#50b0ae]">
                {menu.link.story.name}
              </a>
            </li>
          ))
        }
      </ul>
    </nav>
  </div>
</header>
    

If you go back to the Visual Editor now, you can see your menu being rendered correctly. Feel free to experiment a little bit with it by adding or reordering the entries and saving the Config story.

Fantastic – but what’s actually happening in the code? First of all, we’re using useStoryblokApi to fetch the Config story. What’s important to notice is that an additional parameter – resolve_links – is passed to the apiOptions. This is used to actually get the URLs of the stories that we link internally. You can learn more about this and other parameters in our Content Delivery API docs.

Now, we can loop through our headerMenu data instead of the hardcoded links.

Section titled Wrapping Up Wrapping Up

Congratulations, you have successfully created a dynamic menu in Storyblok and Astro!

Next Part:

Continue reading and learn How to Create Custom Components in Storyblok and Astro.

Author

Dipankar Maikap

Dipankar Maikap

Dipankar is a seasoned Developer Relations Engineer at Storyblok, with a specialization in frontend development. His expertise spans across various JavaScript frameworks such as Astro, Next.js, and Remix. Passionate about web development and JavaScript, he remains at the forefront of the ever-evolving tech landscape, continually exploring new technologies and sharing insights with the community.