Next.js中基于Sanity.IO动态生成导航栏的实现问题求助
实现Next.js+Sanity动态导航栏方案
核心问题分析
你当前的导航栏依赖硬编码的navigationTree,但动态生成的页面(如教练详情页)需要关联Sanity中的slug数据,手动维护链接易出错且无法同步CMS内容。之前通过pageProps传递数据的方式需要每个页面都重复处理,效率低下且易遗漏。
可行实现步骤
1. 在Sanity中定义导航结构Schema
先在Sanity Studio里配置可编辑的导航模型,让导航项能关联动态文档(如教练):
// sanity/schemas/navigationItem.js export default { name: 'navigationItem', type: 'object', title: '导航项', fields: [ { name: 'title', type: 'string', title: '显示标题' }, { name: 'staticSlug', type: 'slug', title: '静态页面链接', optional: true }, { name: 'linkedTrainer', type: 'reference', to: [{ type: 'trainer' }], title: '关联教练页面', optional: true }, { name: 'dropdownItems', type: 'array', of: [{ type: 'navigationItem' }], title: '下拉子菜单', optional: true }, ] } // sanity/schemas/navigation.js export default { name: 'navigation', type: 'document', title: '全局导航菜单', fields: [ { name: 'mainItems', type: 'array', of: [{ type: 'navigationItem' }], title: '主导航项' }, ] }
2. 编写GraphQL查询获取导航数据
创建查询文档获取Sanity中的导航数据,包含关联文档的slug:
# queries/GetNavigation.graphql query GetNavigation { allNavigation { mainItems { title staticSlug { current } linkedTrainer { slug { current } } dropdownItems { title staticSlug { current } linkedTrainer { slug { current } } } } } }
3. 在App组件全局获取导航数据
利用getInitialProps在App组件层面一次性获取导航数据,无需每个页面单独处理:
// pages/_app.tsx import "../styles/globals.css"; import type { AppProps } from "next/app"; import Layout from "../components/layouts/Layout"; import client from "../apollo-client"; import { GetNavigationDocument, GetNavigationQuery } from "../generated/graphql"; import { Navigation } from "../utils/data/Navigation"; function MyApp({ Component, pageProps, navigation }) { return ( <Layout navigation={navigation}> <Component {...pageProps} /> </Layout> ); } // 全局获取导航数据 MyApp.getInitialProps = async () => { const { data } = await client.query<GetNavigationQuery>({ query: GetNavigationDocument, }); // 处理导航数据,生成正确的链接地址 const processNavItems = (items): Navigation => { return items.map(item => { let href = ""; // 优先使用关联的教练页面slug if (item.linkedTrainer) { href = `/trainers/${item.linkedTrainer.slug.current}`; } // 静态页面使用配置的slug else if (item.staticSlug) { href = `/${item.staticSlug.current}`; } return { id: Math.random().toString(36).slice(2), name: item.title, href, current: false, dropdownOptions: item.dropdownItems ? processNavItems(item.dropdownItems) : undefined, }; }); }; const navigation = processNavItems(data.allNavigation[0].mainItems); return { navigation }; }; export default MyApp;
4. 简化Navbar组件
去掉硬编码的navigationTree导入,直接使用App传递的动态导航数据:
// components/Navbar.tsx import { Fragment } from "react"; import Image from "next/image"; import Link from "next/link"; import { Disclosure, Menu, Transition } from "@headlessui/react"; import { MenuIcon, XIcon, ChevronDownIcon } from "@heroicons/react/outline"; import Menulink from "./Menulink"; import { Navigation } from "../../utils/data/Navigation"; type NavBarProps = { navigation: Navigation } function Navbar({ navigation }: NavBarProps) { // 保留原有JSX结构,仅移除硬编码的navigationTree引用 return ( <Disclosure as="nav" className="bg-white shadow-lg fixed w-full top-0 z-50"> {/* 原有导航渲染逻辑不变 */} </Disclosure> ); } export default Navbar;
5. 清理冗余代码
修改教练页面,移除不必要的navigationTree传递:
// pages/trainers/[trainer].tsx // ... 其他导入 type TrainerProps = { name: string | null | undefined; bio: string | null | undefined; bio2: string | null | undefined; bio3: string | null | undefined; url: string | null | undefined; }; export default function Trainer({ name, bio, bio2, bio3, url }: TrainerProps) { // 原有页面逻辑不变 } // ... 其他代码 const transformQueryResponseToProps = (data: AllTrainersQuery): TrainerProps => { const trainers = data?.allTrainer[0]; const { bio, bio2, bio3, name, image } = trainers; const url = image?.asset?.url; return { bio, bio2, bio3, name, url }; };
方案优势
- 动态同步CMS内容:导航项直接从Sanity读取,新增/修改教练或页面时无需手动更新代码
- 全局统一管理:仅在App组件获取一次导航数据,所有页面共享,避免重复逻辑
- 灵活适配动态页面:通过关联文档自动生成正确的动态链接,无需硬编码slug
内容的提问来源于stack exchange,提问作者a-quach
相关产品推荐
相关产品推荐

