Skip to main content

Create Dynamic Menus in Storyblok and Next.js

Contents
    Try Storyblok

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

    Hint:

    Please note that this article has already been updated to match Storyblok V2. If you haven’t already started using it, you can find out how to make the switch here.

    In this part of the tutorial series, we'll make the menu in our header dynamic, so that can manage it directly through Storyblok!

    hint:

    If you are in a hurry, check out this demo in Stackblitz! Alternatively, you can explore or fork the code from the Next Ultimate Tutorial GitHub Repository.

    Requirements

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

    Setup in Storyblok

    First, we will have to create a new content type component where our menu items can be stored. Go to {1} Block Library, and then select {2} + New Block.

    app.storyblok.com
    An annotated screenshot of the Block library in Storyblok
    1
    2

    Name this block, config {1} and then choose the Content Type block {2}.

    app.storyblok.com
    Storyblok editing capabilities
    1
    2

    Next, create a new field with the name header_menu {1} and choose the field type Blocks {2}.

    app.storyblok.com
    Storyblok editing capabilities
    1
    2

    We need menu item links to add to our header_menu, so we need to create a new block. This time, choose the block type {2} Nested Block and {1} name it menu_link.

    app.storyblok.com
    Storyblok editing capabilities
    1
    2

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

    app.storyblok.com
    Storyblok editing capabilities
    1
    2

    We also need to add a name for our menu_link so let's add a new field called, name! {1}Type in name into the field. Since the default field type is text {2}, there is no need to change it. Now to officially add it, click on {3} Add.

    app.storyblok.com
    Storyblok editing capabilities
    1
    2
    3

    Next, we need to make sure that only menu_link blocks are allowed to be added to our header_menu block.

    {1} Choose the config block, and {2} select header_menu

    app.storyblok.com
    Storyblok editing capabilities
    1
    2

    Under the {1} Block Field Options heading and {2} select Allow only specific components to be inserted.

    Then, in the Components Whitelist input field, {3} type in menu_link to add to the whitelist.

    app.storyblok.com
    Storyblok editing capabilities
    1
    2
    3

    There's just one more step left in this setup, and that's to create the Content for our Storyblok space. Go to the {1} Content tab, and select {2} + Create new. Then choose {3} Story

    app.storyblok.com
    Storyblok editing capabilities
    1
    2
    3

    Here, we want to create a new story with the name {1} Config, using our recently created content type {2} Config.

    app.storyblok.com
    Storyblok editing capabilities
    1
    2

    If you open this newly created Config story, you can now {1} add/nest as many menu_link blocks in the header_menu field as you would like. For now, let’s add our {2} About and {3} Blog page.

    app.storyblok.com
    Storyblok editing capabilities
    1
    2
    3

    Rendering the Menu in Next.js

    Now, let's create the code that will render our menu in the frontend of our application. First, let’s review what our imports from the Storyblok React SDK -- storyblokEditable and StoryblokComponent-- do:

    • storyblokEditable makes our components editable in our Real-Time Visual Editor.

    • StoryblokComponent  sets up our page for our Storyblok components.

    Then, let's setup our components: Config.js, HeaderMenu.js, and MenuLink.js to match with our blocks created in Storyblok.

    Config.js

    Config.js
    import { storyblokEditable, StoryblokComponent } from "@storyblok/react";
    import Link from "next/link";
    const Config = ({blok}) => {
      return (
        <div className="relative bg-white border-b-2 border-gray-100" {...storyblokEditable(blok)}>
          <div className="max-w-7xl mx-auto px-4 sm:px-6">
            <div className="flex justify-between items-center  py-6 md:justify-start md:space-x-10">
              <div className="flex justify-start lg:w-0 lg:flex-1">
                <Link href="/">
                  <a>
                    <img
                      className="h-20 w-auto sm:h-10"
                      src='storyblok-primary.png'
                      alt=""
                    />
                  </a>
                </Link>
              </div>
              {blok.header_menu.map((nestedBlok) => (
                <StoryblokComponent className='' blok={nestedBlok} key={nestedBlok._uid} />
              ))}
            </div>
          </div>
        </div>
      );
    };
    export default Config;

    HeaderMenu.js

    HeaderMenu.js
    import { storyblokEditable, StoryblokComponent } from "@storyblok/react";
    const HeaderMenu = ({blok}) => (
        <div className="hidden md:flex items-center justify-end md:flex-1 lg:w-0 space-x-10" {...storyblokEditable({blok})}>
            {blok.links.map((nestedBlok) => (
                <StoryblokComponent blok={nestedBlok} key={nestedBlok._uid} />
            ))}
        </div>
    )
    export default HeaderMenu

    MenuLink.js

    MenuLink.js
    import { storyblokEditable } from "@storyblok/react";
    import Link from "next/link";
    const MenuLink = ({blok}) => (
        <Link href={blok.link.cached_url} {...storyblokEditable(blok)}>
            <a className="text-base font-medium text-gray-500 hover:text-gray-900">
                {blok.name}
            </a>
        </Link>
    )
    export default MenuLink

    Layout.js

    Layout.js
    import Footer from "./Footer";
    import Config from './Config'
    
    const Layout = ({ children, story }) => ( 
      <div>
        <Config blok={story.content} />
          {children}
        <Footer />
      </div>
    );
    
    export default Layout;

    Let’s make sure those components render.

    In _app.js, add your components:

    _app.js
    import Config from "../components/Config";
    import HeaderMenu from "../components/HeaderMenu";
    import MenuLink from "../components/MenuLink";
    import Layout from "../components/Layout";
    
    const components = {
      ...
      config: Config,
      layout: Layout
      "header_menu": HeaderMenu,
      "menu_link": MenuLink,
    };

    Also in _app.js , under the myApp function, add pageProps to the parameters and wrap <Component ... /> in <Layout> like so:

    _app.js
    function MyApp({ Component, pageProps }){ 
      return (
        <Layout story={pageProps.config}>
            <Component {...pageProps} />
        </Layout>
      )
    }

    In index.js and [slug].js, under getStaticProps , add the following code:

    index.js and [slug].js
    export async function getStaticProps() {
      let slug = "home";
    
      let sbParams = {
        version: "draft", // or 'published'
        resolve_links: "url",
      };
    
      const storyblokApi = getStoryblokApi();
      let { data } = await storyblokApi.get(`cdn/stories/${slug}`, sbParams);
      let { data: config } = await storyblokApi.get('cdn/stories/config');
    
      return {
        props: {
          story: data ? data.story : false,
          key: data ? data.story.id : false,
          config: config ? config.story : false,
        },
        revalidate: 3600,
      };
    }

    Now, if you go back to your Storyblok Visual Editor, you should be able to see your menu being rendered! You can add more links, remove them, or even reorder them if you like.

    app.storyblok.com
    Storyblok editing capabilities

    Wrapping Up

    Congratulations, you have successfully created a dynamic menu in Storyblok and Next.js!

    Next Part:

    Continue reading and Create Custom Components in Storyblok and Next.js

    Developer Newsletter

    Want to stay on top of the latest news and updates in Storyblok?
    Subscribe to Code & Bloks - our headless newsletter.

    An error occurred. Please get in touch with marketing@storyblok.com

    Please select at least one option.

    Please enter a valid email address.

    This email address is already registered.

    Please Check Your Email!

    Almost there! To confirm your subscription, please click on the link in the email we’ve just sent you. If you didn’t receive the email check your ’junk folder’ or