Next.js中router.query获取的id刷新页面时返回undefined如何解决?
Next.js 刷新页面时router.query参数报错解决方案
问题原因
这个报错是Next.js Pages Router的路由加载机制导致的:
- 通过
next/link触发客户端跳转时,路由上下文已经完成初始化,router.query可以直接拿到完整的roomId参数,因此功能运行正常 - 手动刷新页面时,首次渲染(包含服务端直出阶段、客户端hydration注水阶段)
router.query初始值为空对象,此时解构得到的roomId为undefined,代码中直接访问rooms[roomId].name等价于读取undefined的属性,直接触发运行时错误。

原业务代码所有依赖roomId的渲染逻辑都没有做参数就绪校验,是触发报错的直接原因。
注:如果你使用Next.js 13+的App Router,直接通过useParams()钩子获取路由参数不会出现首屏空值问题,无需额外做加载判断。
可行解决方案
方案1:增加路由就绪判断(通用兼容方案)
利用router.isReady属性判断路由参数是否解析完成,未就绪时渲染加载占位,等参数拿到后再渲染业务内容,同时增加房间不存在的兜底逻辑,代码示例如下:
import { useRouter } from 'next/router' import styles from '@/styles/Room.module.css' // 其余组件、工具函数引入省略 export default function RoomPage() { const router = useRouter() const { roomId } = router.query // 路由未完成初始化时返回加载态 if (!router.isReady) return <div className={styles.loading}>加载中...</div> // 提前取出当前房间数据,避免重复写rooms[roomId],同时做存在性校验 const currentRoom = rooms[roomId] if (!currentRoom) return <div className={styles.error}>房间不存在或已被解散</div> // 后续业务渲染逻辑,所有原rooms[roomId]替换为currentRoom return ( <> <div className={styles.header}> <div className={styles.title}>{currentRoom.name}</div> <div className={styles.headerCon}> <div className={styles.roomUsers}> <AvatarGroup max={3} total={Object.keys(currentRoom.users).length} > {Object.keys(currentRoom.users).map((userId) => ( <Avatar key={userId} alt={users[userId].name} src={users[userId].profile_pic} /> ))} </AvatarGroup> </div> <div className={styles.headerIcons}> <div className={styles.react}> <AddReactionOutlinedIcon /> </div> <div className={styles.notif}> <NotificationsNoneOutlinedIcon /> </div> <div className={styles.more}> <MoreHorizOutlined /> </div> </div> </div> </div> {user.name === currentRoom.createdBy.name ? ( <div className={styles.begin}> {!roomActive ? ( <button onClick={handleStartSession}>Start</button> ) : ( <button onClick={handleEndSession}>End</button> )} </div> ) : null} <div className={styles.participators}> <div className={styles.pAvatars}> <Avatar alt={currentRoom.createdBy.name} src={currentRoom.createdBy.profile_pic} /> <div className={styles.userName}>{currentRoom.createdBy.name}</div> <div className={styles.userRole}>Admin</div> <div> <audio autoPlay ref={userAudio} /> {peers.map((peer, index) => { return <Audio key={index} peer={peer} />; })} </div> </div> {Object.keys(currentRoom.users) .filter((userId) => users[userId].name !== currentRoom.createdBy.name) .map((userId) => ( <div className={styles.pAvatars} key={userId}> <Avatar alt={users[userId].name} src={users[userId].profile_pic} /> <div className={styles.userName}>{users[userId].name}</div> <div className={styles.userRole}>Member</div> </div> ))} </div> </> ); }
额外修正:原代码用
Math.random()生成列表key会导致每次重渲染都重新挂载组件,引发头像闪烁、音频流重连等问题,上面示例已经改为用唯一的用户ID作为key,符合React列表渲染规范。
方案2:通过getServerSideProps提前注入参数(适合SEO场景)
如果使用Pages Router,可以在服务端侧逻辑中直接解析路由参数,将roomId甚至提前拉取好的房间数据作为props传给页面组件,首屏渲染时就能拿到完整参数,不需要判断isReady:
// 页面组件内新增服务端逻辑 export async function getServerSideProps(context) { const { roomId } = context.params // 可在此处直接请求接口拉取房间详情、用户列表数据,一并传给组件 return { props: { roomId } } }
这种方案首屏没有加载态闪烁,对搜索引擎爬虫更友好,适合公开房间类页面。
方案3:可选链临时兜底(不推荐)
如果不想增加独立加载态,也可以通过可选链操作符+空值兜底暂时避免报错,但这种方式会出现页面内容从“加载中”闪到正常内容的体验问题:
// 所有访问嵌套属性的位置增加可选链 <div className={styles.title}>{rooms?.[roomId]?.name ?? '加载中...'}</div>
内容的提问来源于stack exchange,提问作者Jeoffrey Duke
相关产品推荐
相关产品推荐

