Next.js实战踩坑指南:从入门到部署的完整开发经验分享

东方翠翠 框架 阅读 3,013
赞 89 收藏
二维码
手机扫码查看
反馈

又踩坑了,Next.js 里动态路由参数拿不到?

昨天改一个商品详情页,用的是 Next.js 的动态路由 pages/product/[id].js,结果页面一刷新,router.query.id 是空的!控制台打印出来是 {},但点进去又能正常显示。我人傻了,这不科学啊。

Next.js实战踩坑指南:从入门到部署的完整开发经验分享

一开始我以为是 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 怎么解决的?我都想看看。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论