I have a navigation menu similar to Prismic's navigation. Some navigation has a drop-down menu and some doesn't; see the image below.
How do I create it, and how do I fetch it with Nextjs 14? Thank you in advance!
I have a navigation menu similar to Prismic's navigation. Some navigation has a drop-down menu and some doesn't; see the image below.
How do I create it, and how do I fetch it with Nextjs 14? Thank you in advance!
Hey!
There are many approaches you could take doing this. Actually, we have written a blog article on exactly how we did it for the Prismic website.
Here's how I would build it right now:
In Next.js 14, the most common way to fetch it would be to have some kind of single document custom type, like a "layout" type explained above, which you fetch in your root layout file. Or even a header component, if it's a server component.
In our case, our header changes trough-out the site depending on page type, and we rely on some data from the pages in it, so we have a layout component on each page type, that takes data from the page to decide on how to render, this layout component fetches our single document with the menu items as well.
Tell me if something is unclear!
All the best
/Samuel
Hi Samuelhorn,
What about if some navigation parent doesn't have sub-navigation items? How do we build it?
Hi @solution.stack99 ,
I believe Samuel mentioned that above:
The "Default" variation is your navigation parent that doesn't have sub-navigation items.
I followed that tutorial but I don't know how to fetch the data as there are three slices for this navigation.
In your Next.js page or layout component, you can fetch the layout data. You'll likely want to make this query in your layout component or in each page component, depending on your architecture.
Here's how you might fetch the layout data, including handling variations for menu items with and without sub-menus:
import { client } from '../prismicio';
export async function getLayoutData() {
const layoutData = await client.getSingle('layout', {
graphQuery: `{
layout {
...layoutFields
header {
...headerFields
menu_items {
...menu_itemsFields
variation {
...on MenuItemDefault {
label
link {
...linkFields
}
}
...on MenuItemWithSubMenu {
label
sub_menu {
...sub_menuFields
sub_menu_items {
...sub_menu_itemsFields
}
}
}
}
}
}
}
}`;
, // Adjust according to your custom type's API ID and field
});
return layoutData;
}
After fetching, you can pass the data to your layout or header component and render it accordingly. For example, if you're using server components in Next.js 14, you might pass the fetched layout data as props to your header component:
// In your page or layout component
const layoutData = await getLayoutData();
// Pass layoutData to your Header component
<Header layoutData={layoutData} />
Then, in your Header
component, you would map over the layoutData
to render your menu items and handle any sub-menus based on the structure of your Prismic documents.
Keep in mind that the exact implementation details will depend on your specific project setup, including how you've structured your custom types and documents in Prismic, and how you're handling state and props in your Next.js application. Adjust the field names and structure in the examples above according to your Prismic setup.
Thank you, Phi! I will try it out.
Hey @Phil Thank you for clarifying. I, too, followed the blog to the letter and got stuck fetching the data. It was not clear to me that we have to use GraphQuery for this. And I have two problems with this approach. Hope you can help me understand.
1. Problem: I found it weird that the tsx of the slices is nowhere mentioned. Should the menu item and sub menu item files stay "empty"? So we just use the slices to represent the data?
2. Problem: the query you provided is quite far from what I got after a lot of trial and error. Namely the use of "slices", "primary"...
export async function getLayoutData() {
const client = createClient();
const layoutData = await client.getSingle("layout", {
graphQuery: `{
layout {
...layoutFields
slices {
...on menu_item {
variation {
...on default {
primary {
...primaryFields
}
}
...on withSubMenu {
primary {
label
sub_menu {
slices {
...on sub_menu_item {
variation {
...on default {
primary {
...primaryFields
}
}
}
}
}
}
}
}
}
}
}
}
}`,
});
return layoutData;
}
This fetches the data in what I assume is the correct way. But my typescript is completely confused.
{layout.data.slices.map((slice) => (
<div>
{slice.primary.label}
{slice.variation === "withSubMenu" &&
slice.primary.sub_menu.data.slices.map((sub_menu_item) => (
<div>{sub_menu_item.primary.label}</div>
))}
</div>
))}
Data is not being recognized.
I fixed it with the help of the solutions mentioned here Content relationship types within another content relationship in a Slice - #3 by angeloashmore (although not sure if I used it correctly.
I feel overwhelmed when all I want is a few links under a few links. Hope you can shine some light on what I am doing wrong.
PS: for completeness, here is the rest of the data models:
it will be nice if we had a full video tutorial on how to implement this. Because this "How to Model a Multi-level 'Mega Menu' with Prismic" shows you how to create the needed slices but says nothing about the needed code to fetch the data
Hi @ransbort ,
What framework are you using? Here's an example in Next.js app router.
Here’s how your navigation type might look:
{
"Main menu": {
"type": "Group",
"config": {
"fields": {
"label": { "type": "Text" },
"link_type": {
"type": "Select",
"config": {
"options": ["link", "mega_menu"]
}
},
"link": { "type": "Link" },
"mega_menu_group": {
"type": "Group",
"config": {
"fields": {
"title": { "type": "Text" },
"description": { "type": "Text" },
"link": { "type": "Link" },
"image": { "type": "Image" }
}
}
}
}
}
}
}
app/layout.tsx (or wherever you fetch global nav):
import { createClient } from "@/prismicio";
import { PrismicNextLink } from "@prismicio/next";
import Link from "next/link";
export default async function RootLayout({ children }) {
const client = createClient();
const navigation = await client.getSingle("navigation");
return (
<html>
<body>
<Header navigation={navigation.data} />
{children}
</body>
</html>
);
}
components/Header.tsx:
"use client";
import Link from "next/link";
import { PrismicNextImage, PrismicNextLink } from "@prismicio/next";
import { asText } from "@prismicio/helpers";
import { useState } from "react";
export default function Header({ navigation }) {
const [openMenu, setOpenMenu] = useState(null);
return (
<header className="border-b">
<nav className="flex gap-6 p-4">
{navigation.main_menu.map((item, index) => {
const label = item.label || "Menu";
const type = item.link_type;
if (type === "link") {
return (
<PrismicNextLink key={index} field={item.link}>
{label}
</PrismicNextLink>
);
}
if (type === "mega_menu") {
return (
<div
key={index}
className="relative"
onMouseEnter={() => setOpenMenu(index)}
onMouseLeave={() => setOpenMenu(null)}
>
<button className="hover:underline">{label}</button>
{openMenu === index && (
<div className="absolute left-0 top-full mt-2 bg-white border shadow-lg w-[800px] grid grid-cols-2 gap-4 p-6 z-50">
{item.mega_menu_group.map((groupItem, i) => (
<div key={i} className="flex gap-4">
{groupItem.image && (
<PrismicNextImage
field={groupItem.image}
className="w-16 h-16 object-cover"
/>
)}
<div>
<PrismicNextLink field={groupItem.link}>
<h4 className="font-semibold text-sm mb-1">
{asText(groupItem.title)}
</h4>
</PrismicNextLink>
<p className="text-sm text-gray-600">
{asText(groupItem.description)}
</p>
</div>
</div>
))}
</div>
)}
</div>
);
}
return null;
})}
</nav>
</header>
);
}
Make sure your Tailwind config allows dropdowns:
// tailwind.config.js
module.exports = {
content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
};
Once you publish your navigation document and fill in:
You’ll see a full mega menu dropdown on hover, with titles, images, and links.
thanks so much, for the quick response. I am using nextjs with prismic. i'll try this approach. thanks once again. can i dm you if i'm still having challenges? that way, i can share my private repo with you.
Just reach out here with snippets and errors etc, all relevant info and the team here will be happy to help you.
thanks. i've been able to fetch the sub_menu fields using graphquery. Thanks to the prismic team and the community