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
相关产品推荐
相关产品推荐

