Navigation Menus

Whether your website has thousands of pages or just one, it likely needs lists of links for navigation. This guide describes a simple and scalable way to manage navigation menus within Prismic.


The content models you’ll build throughout this guide support the following key features:

  1. All navigation menus can be managed in Prismic. Content managers will be able to add, edit, and remove links from the menus.
  2. Navigation menus can be reused in multiple contexts. A list of links displayed in the header, for example, could be reused in the footer with different styling.
  3. Top-level links can contain child links when needed. Organizing pages under a parent is a common strategy to simplify top-level navigation menus.
  4. Links can go anywhere. Content managers can link to whatever they need, both within the Prismic repository or out to other sites.

🏃‍♀️ Want to see the final result in action?

Create a new Prismic project with everything described in this guide with the following commands:

npx degit prismicio-community/how-to-nextjs-navigation how-to-nextjs-navigation
cd how-to-nextjs-navigation
npx @slicemachine/init

Content modeling

Two content models are needed:

  • A Navigation custom type
  • A Navigation Item Slice

Create the Navigation Item Slice

The reusable Navigation Item Slice represents navigation links and their optional child links. It will be attached to the Navigation custom type.

Recreate the following Navigation Item in Slice Machine:

NavigationItemnavigation_item
Slice Model
Non-Repeatable Zone
  • NamenameName of the link
    Rich Text
  • LinklinkLink for the item
    Link
Repeatable Zone
  • Child Namechild_nameName of the child link
    Rich Text
  • Child Linkchild_linkLink for the child item
    Link
Copy{
  "id": "navigation_item",
  "type": "SharedSlice",
  "name": "NavigationItem",
  "description": "NavigationItem",
  "variations": [
    {
      "id": "default",
      "name": "Default",
      "docURL": "...",
      "version": "sktwi1xtmkfgx8626",
      "description": "NavigationItem",
      "primary": {
        "name": {
          "type": "StructuredText",
          "config": {
            "label": "Name",
            "placeholder": "Name of the link",
            "allowTargetBlank": false,
            "single": "heading3"
          }
        },
        "link": {
          "type": "Link",
          "config": {
            "label": "Link",
            "placeholder": "Link for the item",
            "allowTargetBlank": true,
            "select": null
          }
        }
      },
      "items": {
        "child_name": {
          "type": "StructuredText",
          "config": {
            "label": "Child Name",
            "placeholder": "Name of the child link",
            "allowTargetBlank": false,
            "single": "heading6"
          }
        },
        "child_link": {
          "type": "Link",
          "config": {
            "label": "Child Link",
            "placeholder": "Link for the child item",
            "allowTargetBlank": true,
            "select": null
          }
        }
      },
      "imageUrl": "https://images.prismic.io/slice-machine/621a5ec4-0387-4bc5-9860-2dd46cbc07cd_default_ss.png?auto=compress,format"
    }
  ]
}

Create the Navigation custom type

The Navigation custom type represents collections of links, such as a website’s header or footer navigation. It uses the Navigation Item Slice created above.

Recreate the following Navigation custom type in Slice Machine as a Reuseable custom type:

Navigationnavigation
Custom Type Model
Static Zone
  • NamenameName of the navigation list
    Rich Text
  • UIDuidUnique ID for the navigation list
    UID
Slice Zone
  • NavigationItem
    Slice
Copy{
  "id": "navigation",
  "label": "Navigation",
  "repeatable": true,
  "status": true,
  "json": {
    "Main": {
      "name": {
        "type": "StructuredText",
        "config": {
          "label": "Name",
          "placeholder": "Name of the navigation list",
          "allowTargetBlank": false,
          "single": "heading1"
        }
      },
      "uid": {
        "type": "UID",
        "config": {
          "label": "UID",
          "placeholder": "Unique ID for the navigation list"
        }
      },
      "slices": {
        "type": "Slices",
        "fieldset": "Slice Zone",
        "config": {
          "choices": {
            "navigation_item": {
              "type": "SharedSlice"
            }
          }
        }
      }
    }
  }
}

Create a Navigation document

Open Prismic and create a Navigation Menu document with content.

Querying

Navigation documents can be queried in a Next.js page’s getStaticProps() function. The document’s data can be used directly in the page or forwarded to a custom reusable component, like a <Layout> or <Navigation> component.

pages/[uid].js
Copy
export async function Page({ navigation, page }) {
  // The `navigation` document can be used here.
}

export async function getStaticProps({ params, previewData }) {
  const client = createClient({ previewData })

  const [navigation, page] = await Promise.all([
    client.getByUID('navigation', 'header'),
    client.getByUID('page', params.uid),
  ])

  return {
    props: {
      navigation,
      page,
    },
  }
}

Learn more about querying in the Fetch Data article.

Templating

The navigation menu data returned from Prismic contains an array of Slices holding your links.

The following <Navigation> component is one way to loop through each link and render it. You can customize the component by adding your own styling.

It makes use of @prismicio/react's <PrismicLink> component which requires Next.js-specific set up. See the Set up Prismic article for details.

components/Navigation.js
Copy
import { PrismicLink, PrismicText } from '@prismicio/react'

export function Navigation({ navigation }) {
  return (
    <nav>
      <ul>
        {/* Renders top-level links. */}
        {navigation.data.slices.map((slice) => {
          return (
            <li key={slice.id}>
              <PrismicLink field={slice.primary.link}>
                <PrismicText field={slice.primary.name} />
              </PrismicLink>

              {/* Renders child links, if present. */}
              {slice.items.length > 0 && (
                <ul>
                  {slice.items.map((item) => {
                    return (
                      <li key={JSON.stringify(item)}>
                        <PrismicLink field={item.child_link}>
                          <PrismicText field={item.child_name} />
                        </PrismicLink>
                      </li>
                    )
                  })}
                </ul>
              )}
            </li>
          )
        })}
      </ul>
    </nav>
  )
}

The <Navigation> component can be used on a page like so:

pages/[uid].js
Copy
import { Navigation } from '../components/Navigation'

export async function Page({ navigation, page }) {
  return (
    <div>
      <Navigation navigation={navigation} />
      {/* The rest of your component... */}
    </div>
  )
}

export async function getStaticProps({ params, previewData }) {
  const client = createClient({ previewData })

  const [navigation, page] = await Promise.all([
    client.getByUID('navigation', 'header'),
    client.getByUID('page', params.uid),
  ])

  return {
    props: {
      navigation,
      page,
    },
  }
}

Learn more about templating content in the Template Content article.

Going further

The navigation strategy shared in this article can be used as-is or as a base for your own custom navigation strategy.

Here are a few ideas you can try to customize your navigation menus:

  • Allow content managers to select an icon for each link and display them in the menu.
  • Support short descriptions for top-level navigation items.
  • Automatically display an “external link” icon next to links pointing to external websites.
  • If you don’t need child links, remove the Repeatable Zone fields from the NavigationItem Slice and remove its accompanying React code.

Was this article helpful?
Not really
Yes, Thanks

Can't find what you're looking for? Spot an error in the documentation? Get in touch with us on our Community Forum or using the feedback form above.