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

NextJS 13.4.7中useMediaQuery客户端钩子报错问题咨询

NextJS 13.4.7 客户端组件触发'use client-only hook'错误排查

问题描述

使用NextJS 13.4.7开发应用,Header组件已添加"use client"声明作为客户端组件,被服务端组件引入,但调用自定义useMediaQuery钩子时仍持续收到'use client-only hook'错误,官方文档表明该用法应被允许。

核心代码

"use client";

import { useContext, useState, useEffect } from "react";
import PageDataContext from "@/contexts/PageDataContext";
import useMediaQuery from "@/hooks/useMediaQuery";
import { motion } from "framer-motion";

export default function Header() {
  const content = useContext(PageDataContext);
  const isSmallDevice = useMediaQuery("only screen and (max-width : 991px)");
  const [hideNav, setHideNav] = useState(isSmallDevice);
  const { home: homeContent } = content;
  const { navigation: navigationContent } = content;
  const { name } = homeContent;
  const [firstName, lastName] = name.split(" ");

  const variants = {
    open: { opacity: 1, x: "0%" },
    closed: { opacity: 0, x: "100%" },
  };
  const toggleSideNav = () => {
    setHideNav(!hideNav);
  };

  useEffect(() => {
    setHideNav(isSmallDevice);
  }, [isSmallDevice]);

  return (
    <header id="site_header" className="header">
      <div className="header-content clearfix">
        <div className="text-logo">
          <a href="/">
            <div className="logo-symbol">{firstName[0].toUpperCase()}</div>
            <div className="logo-text">
              {firstName} <span>{lastName}</span>
            </div>
          </a>
        </div>
        <motion.div
          animate={hideNav ? "closed" : "open"}
          variants={variants}
          style={{
            position: "fixed",
            width: "100%",
            height: "calc(100% - 52px)",
            top: "52px",
            left: "auto",
          }}
        >
          <div className="site-nav">
            <ul className="leven-classic-menu site-main-menu">
              {navigationContent.map((item, idx) => (
                <li className="menu-item" key={"header_key_" + idx}>
                  <a href={item.url}>{item.title}</a>
                </li>
              ))}
            </ul>
          </div>
        </motion.div>
        <a className="menu-toggle mobile-visible" onClick={toggleSideNav}>
          <i className="fa fa-bars"></i>
        </a>
      </div>
    </header>
  );
}

排查与解决步骤

1. 检查自定义useMediaQuery钩子的客户端标记

自定义钩子useMediaQuery内部必然用到了useEffect/useState等客户端专属API,但如果钩子所在文件未添加"use client"声明,默认会被当作服务端组件处理,调用时就会触发错误。

解决: 在@/hooks/useMediaQuery文件顶部添加"use client";声明。

2. 验证上下文提供者的执行环境

如果PageDataContext.Provider所在的组件是服务端组件,且上下文值包含非序列化内容(如函数、DOM对象),或者提供者本身用到了客户端API但未标记"use client",会间接导致客户端组件的上下文调用报错。

解决:

  • 确保PageDataContext.Provider所在组件若使用客户端API,需添加"use client";
  • 上下文传递的content必须是可序列化的JSON数据,避免包含非序列化对象。

3. 检查组件引入链路的服务端逻辑

若服务端组件在引入Header时,存在条件渲染依赖客户端状态、将Header作为参数传递给服务端函数等操作,会导致NextJS误判组件执行环境。

解决: 服务端组件仅直接引入并渲染Header,不在服务端逻辑中处理Header的客户端相关状态。

4. 清理缓存与重启开发服务

NextJS 13.4.x部分小版本存在客户端组件标记的缓存bug,尝试:

  • 删除.next文件夹及node_modules/.cache目录;
  • 重启开发服务器;
  • 确认next.config.js未禁用React Strict Mode(Strict Mode可能放大潜在bug)。

代码优化建议

原代码中useState初始化后又用useEffect同步isSmallDevice的逻辑可简化,避免状态不同步:

// 替换原有的hideNav状态与useEffect
const [isManuallyToggled, setIsManuallyToggled] = useState(false);
// 优先使用手动切换的状态,否则遵循媒体查询结果
const hideNav = isManuallyToggled ? !isSmallDevice : isSmallDevice;

const toggleSideNav = () => {
  setIsManuallyToggled(!isManuallyToggled);
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 02:34:52