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 Next.js

Try Storyblok

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

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, you can explore or fork the code from the Next Ultimate Tutorial GitHub Repository.

Section titled Requirements 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.

Section titled Setup in Storyblok 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.

An annotated screenshot of the Block library in Storyblok
1
2

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

1
2

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

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.

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}.

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.

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

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.

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

1
2
3

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

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.

1
2
3

Section titled Rendering the Menu in Next.js 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, update getStaticProps function, with the following code:

index.js and [slug].js
        
      import Head from "next/head";
import {
  useStoryblokState,
  getStoryblokApi,
  StoryblokComponent,
} from "@storyblok/react";
import Layout from "../components/Layout";
 
export default function Home({ story }) {
  story = useStoryblokState(story);
 
  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
    <Layout>
      <StoryblokComponent blok={story.content} />
    </Layout>
    </div>
  );
}
 
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.

Section titled Wrapping Up 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

Author

Cat Ballar

Cat Ballar

Cat is a Developer Relations Engineer at Storyblok. She is a multi-faceted engineer with disciplines in both UX Design and UI/frontend Development.