如何在 Next.js 中实现用户友好的动态面包屑导航

使用 Next.js 和最新的 App Router 系统,学习如何实现动态面包屑导航,为用户提供清晰的路径导航和更好的页面层次结构。完整示例代码和优化建议,适合初学者和开发者。

在构建用户友好且直观的 Web 应用程序时,而面包屑菜单是一项有用的导航功能。面包屑为用户提供了在应用程序层次结构中他们所处位置的清晰路径,并使他们可以轻松返回到上一页。在本教程中,我们将探索如何在nextjs使用内置路由系统和新的布局页面在 Next.js 应用程序中创建面包屑导航。

准备工作

在开始创建面包屑菜单之前,请确保您已在您的开发机器上安装了 Node.js、npm、typescript。 初始化一个新的 Next.js 应用程序。

npx create-next-app@latest 

在接下来的询问环节中, 选项全部选择默认:

✔ Would you like to use ESLint? … **No** / Yes
✔ Would you like to use Tailwind CSS? … No / **Yes**
✔ Would you like to use `src/` directory? … No / **Yes**
✔ Would you like to use App Router? (recommended) … No / **Yes**
✔ Would you like to customize the default import alias? … **No** / Yes

创建一个面包屑组件


export interface Crumb {
  label: string;
  href: string;
}

import Link from 'next/link';

interface BreadcrumbsProps {
  crumbs: Crumb[];
}
function Breadcrumbs( { crumbs }: BreadcrumbsProps ) {
  return (
    <nav aria-label="breadcrumb">
      <ol className="breadcrumb">
        {crumbs.map((crumb, index) => (
          <li key={crumb.href} className="breadcrumb-item">
            {index === crumbs.length - 1 ? (
              <span>{crumb.label}</span>
            ) : (
              <Link href={crumb.href}>{crumb.label}</Link>
            )}
          </li>
        ))}
      </ol>
    </nav>
  );
}

export default Breadcrumbs;

在页面中获取数据 传递给Breadcrumbs

在页面中,我们需要我们获取需要的路径,Next.js 路由中动态面包屑导航的实现机制主要依赖于 next/router 钩子和两个关键属性:asPathpathname

import { usePathname } from 'next/navigation'

const paths = usePathname()
const pathNames = paths.split('/').filter( x => x )

我们使用usePathname 获取到当前路径后,把他转换为我们需要的格式。 假设用户访问 youdomain.com/post/abc/comments paths就是/post/abc/comments 转换完成后,我们的数据就是["post", "abc","comments"] 然后我们把数据传递给组件Breadcrumbs就可以了。

   <div>
      <Breadcrumbs crumbs={crumbs} />
      {/* 页面内容 */}
    </div>

这就是是一个基本的静态面包屑的实现方式。

动态路由的面包屑导航

对于动态路由,我们需要额外的逻辑来处理路径参数。这里展示一个完整的示例:

// src/app/[...slug]/page.tsx
'use client';

import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
import Breadcrumbs, { Crumb } from '@/components/Breadcrumbs';

// 静态路径配置
const STATIC_SEGMENTS: Record<string, string> = {
  posts: 'Posts',
  users: 'Users',
  settings: 'Settings',
  // 更多静态路径
};

export default function DynamicPage() {
  const pathname = usePathname();
  const [breadcrumbs, setBreadcrumbs] = useState<Crumb[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!pathname) return;

    const generateBreadcrumbs = async () => {
      const segments = pathname.split('/').filter(Boolean); // 分离路径段
      const crumbs: Crumb[] = [];

      const requests = segments.map(async (segment, index) => {
        const path = `/${segments.slice(0, index + 1).join('/')}`;
        const isLast = index === segments.length - 1;

        // 处理静态路径段
        if (STATIC_SEGMENTS[segment]) {
          crumbs.push({ href: path, label: STATIC_SEGMENTS[segment] });
          return;
        }

        // 处理动态路径段
        try {
            //你的后端接口
          const response = await fetch(`/api/entities/${segment}`);
          if (!response.ok) throw new Error('Failed to fetch segment data');
          const data = await response.json();
          crumbs.push({ href: path, label: data.title || `Item ${segment}` });
        } catch (error) {
          console.error(`Error fetching breadcrumb for segment "${segment}":`, error);
          crumbs.push({ href: path, label: `Item ${segment}` });
        }
      });

      await Promise.all(requests); // 并行化请求
      setBreadcrumbs(crumbs);
      setLoading(false);
    };

    generateBreadcrumbs();
  }, [pathname]);

  if (loading) {
    return <div className="animate-pulse h-8 bg-gray-200 rounded w-full max-w-md" />;
  }

  return (
    <div className="container mx-auto px-4">
      <Breadcrumbs crumbs={breadcrumbs} />
      {/* 页面内容 */}
    </div>
  );
}

主要内容:

代码的核心逻辑位于 useEffect 钩子中,该钩子会在路径名 pathname 发生变化时执行。 钩子内部定义了一个异步函数 generateBreadcrumbs,该函数负责根据路径名生成面包屑导航数据。

对于静态路径段,代码会直接从 STATIC_SEGMENTS 对象中查找对应的标签,并将其添加到 crumbs 数组中。

对于动态路径段,代码会尝试从后端 API 获取数据。 使用 fetch API 发送请求,并将路径段作为参数传递给 API。如果请求成功,代码会将 API 返回的数据中的 title 属性作为标签,并将其添加到 crumbs 数组中。如果请求失败,代码会使用默认标签 Item ${segment}。

为了提高效率,使用 Promise.all 方法并行化所有请求。在所有请求完成后,将 crumbs 数组更新到 breadcrumbs 状态变量中,并将 loading 状态变量设置为 false,表示数据加载完成。 最后,代码根据 loading 状态变量的值渲染不同的内容。 如果数据正在加载,代码会显示一个加载动画。如果数据加载完成,代码会使用 Breadcrumbs 组件渲染面包屑导航,并显示页面内容。

总结

通过本教程,我们实现了一个功能完整的动态面包屑导航组件,支持静态和动态路由。这个实现方案可以根据具体项目需求进行调整和扩展,添加可访问性支持和错误处理等。记住要根据实际使用场景来选择合适的功能特性。

微信公众号

使用 Hugo 构建
主题 StackJimmy 设计