You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 12:27:23