Shopify Menus
Replace hardcoded nav and footer menus with Shopify-powered menus, and optionally add a megamenu component.
How to use
Enable Shopify Menus
By default, the storefront uses hardcoded navigation links and an empty footer. This skill replaces them with dynamic menus fetched from Shopify, and optionally adds a full-featured megamenu component.
Before you start
Ask the user three questions in order:
1. Which menus do you want to fetch from Shopify?
- Nav menu — replaces the hardcoded quick links in the header
- Footer menu — adds Shopify-powered footer columns
- Both
2. What are the Shopify menu handles?
Ask for each selected menu. Defaults: main-menu for nav, footer for footer.
3. Do you want to add a megamenu component?
A megamenu adds a multi-level category browser to the nav bar with:
- 3-level hierarchy (top-level items → subcategories → leaf links)
- Desktop hover interaction with mouse direction tracking
- Mobile accordion overlay via the bottom bar
- BroadcastChannel cross-tab sync
This requires a Shopify menu with up to 3 levels of nesting. The user can use the same nav menu handle or a separate one.
Wait for the user to answer all questions before proceeding.
Part A: Enable Shopify nav menu
Skip this section if the user did not select the nav menu.
A1. Update components/layout/nav/quick-links.tsx
Replace the hardcoded links array with a Shopify menu fetch. Make the component async:
Replace "NAV_HANDLE" with the handle the user provided.
A2. Update components/layout/nav/index.tsx
Since QuickLinks is now async, wrap it in <Suspense> and pass locale:
Part B: Enable Shopify footer menu
Skip this section if the user did not select the footer menu.
B1. Update components/layout/footer.tsx
Add the Shopify menu fetch back to the footer. Create a FooterContent async component:
Replace "FOOTER_HANDLE" with the handle the user provided.
Part C: Add megamenu component
Skip this section if the user did not want a megamenu.
Prerequisites
- A Shopify Navigation menu exists with up to 3 levels of nesting
react-remove-scrollis installed (pnpm add react-remove-scroll)
Data model
The megamenu transforms a Shopify 3-level nested menu into this hierarchy:
| Type | Level | Description |
|---|---|---|
MegamenuItem | 1 | Top-level nav trigger (e.g. "Clothing") |
MegamenuPanel | 2 | Subcategory grouping (e.g. "Tops") |
MegamenuCategory | 3 | Leaf link (e.g. "T-Shirts") |
C1. Install dependency
C2. Create lib/shopify/types/megamenu.ts
Define the four types (MegamenuCategory, MegamenuPanel, MegamenuItem, MegamenuData) exactly as shown in the data model above.
C3. Create lib/shopify/operations/megamenu.ts
Fetch the Shopify menu by handle and transform it into MegamenuData:
Replace "MENU_HANDLE" with the megamenu handle the user provided.
This relies on getMenu() from lib/shopify/operations/menu.ts which already exists and supports 3-level nesting with "use cache: remote", cacheLife("max"), and cacheTag("menus").
C4. Create megamenu components
Create a directory components/layout/nav/megamenu/ with the following files:
C4a. menu-trigger-icon.tsx
A simple SVG hamburger icon component:
C4b. mouse-safe-area.tsx
A UX utility that prevents accidental menu switches when moving diagonally toward the content panel. It creates an invisible clipped polygon between the trigger column and the panel:
C4c. megamenu-panel.tsx
Renders a single panel's header and category links. Supports both internal (Next.js Link) and external (<a>) links:
C4d. megamenu-client.tsx
The desktop megamenu client component. This is the largest component and includes:
- BroadcastChannel sync — cross-tab open/close/toggle coordination
- Hover intent detection — delays switching when mouse moves toward the panel (150ms), switches immediately otherwise
- Keyboard navigation — Escape to close
- RemoveScroll — prevents body scroll when menu is open
- Full-height overlay — backdrop blur with gradient
Key behavior:
- Top-level items render as
Link(internal),<a>(external), or<button>(no href) - Each item uses
data-activeattribute for styling the active indicator dot - Active item's panels render in the right column
- A "Show all " link appears below the panels when the active item has an
href
The component accepts items: MegamenuItem[] and optional children (rendered in a footer below the nav list).
It uses translation keys from the nav namespace:
categories— trigger button labelexploreCategories— heading above the nav listshowAllCategory— "Show all " link text (with{category}interpolation)
Export both MegamenuClient and MegamenuFallback from this file. The fallback renders a disabled-looking trigger with the hamburger icon and "Browse" label.
C4e. megamenu-desktop.tsx
A thin server component wrapper that renders MegamenuClient only when items are non-empty:
C4f. megamenu-mobile.tsx
The mobile megamenu component using the shadcn Accordion component. Key differences from desktop:
- Uses
Accordion(type="single",collapsible) for expand/collapse - Top-level items with sub-items get an accordion trigger; items with only an href get a plain link
- No hover intent — touch-only interaction
- Uses the same BroadcastChannel sync, RemoveScroll, and Escape key handling
- Panels render inline within the accordion content
The component accepts data: MegamenuData and optional children.
Export both MegamenuMobile and MegamenuMobileFallback (renders null).
C4g. index.tsx (barrel)
The main entry point. A server component that fetches data and renders both layouts:
C5. Add translation keys
Add the following keys to all locale files under lib/i18n/messages/ in the nav namespace (if not already present):
C6. Wire into the nav
Import and render the Megamenu component in components/layout/nav/index.tsx, passing locale:
Place it between the logo and the quick-links.
C7. Ensure the Accordion component exists
The mobile megamenu requires the shadcn Accordion component. If it doesn't exist yet:
C8. Add Browse toggle to the bottom bar
The bottom bar (components/layout/bottom-bar.tsx) should include a Browse button that toggles the megamenu on mobile. Add:
- Import
MenuTriggerIconfrom./nav/megamenu/menu-trigger-iconandXfromlucide-react - Add state:
const [menuOpen, setMenuOpen] = useState(false) - Add BroadcastChannel listener for
"megamenu"close events (in auseEffect) - Add a
toggleMenufunction that posts{ type: "toggle" }on the"megamenu"BroadcastChannel - Add the toggle button before the search button in the bottom bar:
C9. Add collection breadcrumb ancestor paths (optional)
To show rich breadcrumbs on collection pages (e.g. Home / Clothing / Tops / T-Shirts), create lib/utils/breadcrumbs.ts with:
buildCollectionAncestorPath(handle, menu)— walks the megamenu tree to find a collection by its/collections/{handle}href and returns ancestor segmentsbuildProductCategoryPath(category, menu, collectionHandles?)— finds the deepest menu path for a product by matching its collection handles against megamenu hrefs
Then update components/collections/header.tsx and components/collections/structured-data.tsx to:
- Import
getMegamenuDataandbuildCollectionAncestorPath - Add
getMegamenuData(locale)to theirPromise.allcalls - Use
buildCollectionAncestorPath(handle, menu)to render ancestor breadcrumb segments before the current collection title
Guardrails
- The
getMenu()operation fromlib/shopify/operations/menu.tsalready handles caching ("use cache: remote",cacheTag("menus")) and URL transformation. Do not duplicate that logic. - Components in
components/layout/nav/megamenu/may import from@/lib/shopify/types/megamenufor prop types, but must not call Shopify operations directly — data fetching happens in the server component barrel (index.tsx). - All user-visible strings must use
useTranslations("nav")— no hardcoded English text in components. - The BroadcastChannel name must be
"megamenu"for cross-tab sync to work. - External links (starting with
http) must use<a>withtarget="_blank"andrel="noopener noreferrer". Internal links must use Next.jsLink. - The mobile component renders on all viewports but is only visible below
md. Desktop useshidden md:block.