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 and Templating

Navigation documents can be queried in a Next.js layout file and called using the RootLayout function. This way, it's only queried once and added to all the pages above or below the page content.

The data is queried within the <Navigation> component using the getByUID function. UPDATE THE UID TO MATCH YOURS. 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/next's <PrismicNextLink> component.

  • Full File
  • Navigation component
  • RootLayout
Full File
src/app/layout.js
Copy
import { PrismicText } from "@prismicio/react";
import { PrismicNextLink, PrismicPreview } from "@prismicio/next";
import { createClient, repositoryName } from "@/prismicio";

/**
 * @param {{ children: React.ReactNode }}
 */
export default async function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="overflow-x-hidden antialiased">
        {/* @ts-expect-error Async Server Component */}
        <Navigation/>
        {children}
        <PrismicPreview repositoryName={repositoryName} />
      </body>
    </html>
  );
}

async function Navigation() {
  const client = createClient();
  const navigation = await client.getByUID("navigation", "header-menu"); // UPDATE THE UID TO MATCH YOURS

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

              {/* Renders child links, if present. */}
              {slice.items.length > 0 && (
                <ul>
                  {slice.items.map((item) => {
                    return (
                      <li key={JSON.stringify(item)}>
                        <PrismicNextLink field={item.child_link}>
                          <PrismicText field={item.child_name} />
                        </PrismicNextLink>
                      </li>
                    )
                  })}
                </ul>
              )}
            </li>
          )
        })}
      </ul>
    </nav>
  );
}
Navigation component
Copy
async function Navigation() {
  const client = createClient();
  const navigation = await client.getSingle("navigation");

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

              {/* Renders child links, if present. */}
              {slice.items.length > 0 && (
                <ul>
                  {slice.items.map((item) => {
                    return (
                      <li key={JSON.stringify(item)}>
                        <PrismicNextLink field={item.child_link}>
                          <PrismicText field={item.child_name} />
                        </PrismicNextLink>
                      </li>
                    )
                  })}
                </ul>
              )}
            </li>
          )
        })}
      </ul>
    </nav>
  );
}
RootLayout
Copy
export default async function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="overflow-x-hidden antialiased">
        {/* @ts-expect-error Async Server Component */}
        <Navigation/>
        {children}
        <PrismicPreview repositoryName={repositoryName} />
      </body>
    </html>
  );
}

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.
  • Learn more about querying in the Fetch Data article.
  • Learn more about templating content in the Template Content article.

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.