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

Next.js动态路由:如何跳转至React组件渲染书籍详情?

解决方案:导航至React组件渲染书籍详情

你的核心问题是混淆了API路由(返回JSON数据)和页面组件(渲染UI)的作用,下面分三种可行方案实现跳转并渲染详情:


一、基础方案:客户端导航+详情页异步获取数据

1. 新建书籍详情页面组件

在pages目录下创建books/[id].js(这是页面组件,不是API路由),用于渲染书籍详情UI:

import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';

export default function BookDetailsPage() {
  const router = useRouter();
  const { id } = router.query;
  const [book, setBook] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

    async function fetchBook() {
      try {
        const res = await fetch(`/api/book/${id}`);
        if (!res.ok) throw new Error('获取书籍失败');
        const data = await res.json();
        setBook(data.book);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchBook();
  }, [id]);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误:{error}</div>;
  if (!book) return <div>未找到该书籍</div>;

  return (
    <div className="book-details">
      <img src={book.image} alt={book.title} style={{ width: '200px' }} />
      <h1>{book.title}</h1>
      <p>作者:{book.author}</p>
      <p>价格:{book.price}</p>
      {/* 添加更多详情字段 */}
    </div>
  );
}

2. 修改Card组件的跳转逻辑

把原来跳转到API路由的路径改成新的页面路径,同时可以移除弹窗相关代码(如果不需要弹窗):

function handleBookClick(book, id) {
  // 移除setSelectedBook,因为跳转后是独立页面
  router.push({
    pathname: `/books/${id}`,
  });
}

二、静态预渲染方案:使用getStaticProps(适合数据不频繁更新的场景)

如果你的书籍数据更新频率低,可以用getStaticProps在构建时预渲染所有详情页,提升访问速度:

import Book from '../../model/Book';

export default function BookDetailsPage({ book }) {
  if (!book) return <div>未找到该书籍</div>;

  return (
    <div className="book-details">
      <img src={book.image} alt={book.title} style={{ width: '200px' }} />
      <h1>{book.title}</h1>
      <p>作者:{book.author}</p>
      <p>价格:{book.price}</p>
    </div>
  );
}

// 预生成所有书籍的访问路径
export async function getStaticPaths() {
  const books = await Book.find({}, '_id');
  const paths = books.map(book => ({
    params: { id: book._id.toString() },
  }));

  return {
    paths,
    fallback: 'blocking', // 新增书籍时自动动态生成页面
  };
}

// 构建时获取对应书籍的数据
export async function getStaticProps({ params }) {
  try {
    const book = await Book.findById(params.id);
    if (!book) {
      return { notFound: true };
    }

    // 将MongoDB文档转为普通对象,避免序列化问题
    return {
      props: { book: JSON.parse(JSON.stringify(book)) },
      revalidate: 86400, // 每天重新生成页面(可选)
    };
  } catch (error) {
    return { notFound: true };
  }
}

三、动态渲染方案:使用getServerSideProps(适合实时更新的数据)

如果书籍数据需要实时展示最新状态,用getServerSideProps在每次请求时从数据库获取最新数据:

import Book from '../../model/Book';
import { StatusCodes } from 'http-status-codes';

export default function BookDetailsPage({ book }) {
  if (!book) return <div>未找到该书籍</div>;

  return (
    <div className="book-details">
      <img src={book.image} alt={book.title} style={{ width: '200px' }} />
      <h1>{book.title}</h1>
      <p>作者:{book.author}</p>
      <p>价格:{book.price}</p>
    </div>
  );
}

export async function getServerSideProps({ params, res }) {
  try {
    const book = await Book.findById(params.id);
    if (!book) {
      res.statusCode = StatusCodes.NOT_FOUND;
      return { props: { book: null } };
    }

    return {
      props: { book: JSON.parse(JSON.stringify(book)) },
    };
  } catch (error) {
    res.statusCode = StatusCodes.INTERNAL_SERVER_ERROR;
    return { props: { book: null } };
  }
}

关键注意事项

  • 区分API路由和页面组件:API路由仅用于返回数据,页面组件负责渲染UI,不要跳转到API路由。
  • 使用getStaticProps/getServerSideProps时,直接在服务器端操作数据库,无需调用自身API路由,效率更高。
  • 若需保留原弹窗+跳转的混合模式,可以同时保留selectedBook状态和跳转逻辑,根据需求切换。

内容的提问来源于stack exchange,提问作者MaksymY

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 09:15:23