Next.js实战踩坑指南:从入门到部署的完整开发经验分享
又踩坑了,Next.js 里动态路由参数拿不到?
昨天改一个商品详情页,用的是 Next.js 的动态路由 pages/product/[id].js,结果页面一刷新,router.query.id 是空的!控制台打印出来是 {},但点进去又能正常显示。我人傻了,这不科学啊。
一开始我以为是 SSR 的问题,毕竟 Next.js 默认是服务端渲染,可能在服务端拿不到动态参数?但不对啊,官方文档明明说动态路由在服务端也能通过 getServerSideProps 拿到 params。我赶紧翻了下代码,发现我压根没写 getServerSideProps,就直接在组件里用 useRouter() 拿的。这就尴尬了。
折腾了半天,原来是 hydration 阶段的问题
我试了第一种方法:加个判断,如果 router.isReady 为 false 就 return null。这样确实能避免报错,但页面会闪一下白屏,体验很差。而且用户看到的是空白,再突然加载内容,很不友好。
// 别这么干,虽然能跑但体验差
import { useRouter } from 'next/router';
export default function ProductDetail() {
const router = useRouter();
if (!router.isReady) return null; // 白屏警告!
const { id } = router.query;
return <div>Product ID: {id}</div>;
}
后来我查了 Next.js 的 issue,发现很多人遇到类似问题。核心原因是:在服务端渲染(SSR)或静态生成(SSG)时,router.query 在首次渲染阶段是空的,因为路由还没「hydrate」完成。只有客户端 JS 加载完、React 挂载后,router.isReady 才会变成 true,这时候才能安全读取参数。
但我不想让用户看到白屏,也不想搞复杂的状态管理。有没有更优雅的办法?
最终方案:用 getServerSideProps 提前注入数据
我突然意识到——既然服务端能拿到参数,为什么不直接在服务端把数据准备好,传给组件呢?这样组件就不用自己去 router 里捞了,也避免了 hydration 不一致的问题。
于是我把逻辑移到了 getServerSideProps 里:
export async function getServerSideProps(context) {
const { id } = context.params; // 这里能直接拿到动态参数!
// 假装这里去数据库查商品
const product = await fetchProductById(id);
if (!product) {
return {
notFound: true, // 如果商品不存在,返回404
};
}
return {
props: {
product,
id, // 也可以把 id 传下去,虽然 product 里可能已经有
},
};
}
export default function ProductDetail({ product }) {
// 现在直接用 props,不需要 router.query 了
return <div>Product: {product.name}</div>;
}
亲测有效!页面第一次加载就有完整内容,没有白屏,SEO 也友好。而且连 router.isReady 都不用判断了,清爽。
踩坑提醒:这三点一定注意
- 不要在组件顶层直接解构 router.query,比如
const { id } = useRouter().query,这在 SSR 阶段会出问题,因为 query 是空对象,解构可能得到 undefined,但如果你用了 TypeScript,可能还会报类型错误。 - 如果必须用客户端路由参数(比如做 SPA 跳转后的处理),一定要配合 router.isReady 使用,但尽量避免这种场景,优先考虑服务端预取。
- 动态路由文件名要和 params 对应,比如
[productId].js对应context.params.productId,别手抖写成id,否则拿不到。
其实还有个方案是用 getStaticPaths + getStaticProps 做静态生成,适合商品数量不多、能预知 ID 的场景。但我们的商品是用户动态上传的,ID 不确定,所以只能用 getServerSideProps。不过原理差不多,都是在服务端就把数据准备好。
额外小问题:改完后有个小瑕疵
上线后测试发现,如果用户直接输入一个不存在的 ID,比如 /product/999999,页面会先闪一下 loading(因为我们有全局 loading 动画),然后才跳 404。这是因为 getServerSideProps 要等 fetch 完才知道商品不存在,没法提前拦截。
这个其实无大碍,毕竟 404 页最终还是正确返回了。如果真想优化,可以加个缓存层,或者对高频无效 ID 做黑名单,但现阶段没必要折腾。小问题,忍了。
核心代码就这几行,但思路很重要
总结一下:在 Next.js 里,凡是依赖动态路由参数的页面,**优先考虑在 getServerSideProps 或 getStaticProps 中处理**,而不是在组件里等客户端去拿。这样既能保证首屏内容完整,又能避免 hydration 不一致的警告(比如 React 报 “Text content did not match”)。
我之前老想着“组件自己管自己的数据”,结果在 Next.js 这种 SSR 框架里反而绕了远路。现在明白了:Next.js 的哲学就是“服务端能做的,就别拖到客户端”。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如有没有办法在客户端优雅地处理参数缺失而不白屏?或者你用 SWR / React Query 怎么解决的?我都想看看。

暂无评论