在Next.js中无需将所有组件转为客户端组件获取窗口宽度
问题分析与解决方案
你遇到的错误是因为服务端组件(Server Components)无法调用带有'use client'指令的钩子或组件。服务端渲染阶段不存在window对象,而你的useWindowSize钩子依赖浏览器环境的window,直接在服务端组件Header中使用必然触发错误。
不需要把整个Header转为客户端组件,只需将依赖窗口尺寸的部分拆成独立的客户端组件,原Header依然保留服务端组件的特性(比如异步获取categories数据)。
具体实现步骤
1. 拆分客户端子组件
创建一个专门处理响应式逻辑的客户端组件,比如ResponsiveNav.tsx,把需要用到useWindowSize的逻辑移到这里:
'use client' import Link from 'next/link' import styles from '@/app/page.module.scss' import SignInButton from './SignInButton' import ThemeToggleButton from './ThemeToggleButton' import useWindowSize from '@/utils/constants/useWindowSize' import screenSize from '@/utils/constants/mediaQueries' type Props = { categories: Array<{ slug: string; name: string }> } export default function ResponsiveNav({ categories }: Props) { const { nav, navLink } = styles const windowSize = useWindowSize() // 根据窗口尺寸渲染不同导航布局 return ( <nav className={nav}> {windowSize && windowSize > screenSize.mobile ? ( categories.map((category) => ( <Link key={category.slug} href={`category/${category.slug}`} className={navLink} > {category.name} </Link> )) ) : ( // 移动端替换为汉堡菜单或其他布局 <button className={styles.mobileMenuBtn}>菜单</button> )} <ThemeToggleButton /> <SignInButton /> </nav> ) }
2. 修改原Header服务端组件
移除useWindowSize的导入,引入上述客户端组件,将服务端获取的categories数据传递过去:
import Link from 'next/link' import styles from '@/app/page.module.scss' import { getCategories } from '@/services' import Image from 'next/image' import ResponsiveNav from '../buttons/ResponsiveNav' type Props = {} async function Header({}: Props) { const { header, logo } = styles const categories = await getCategories() return ( <header className={header}> <div className={logo}> <Link href="/"> <Image src="/icons/hsb-icon.png" style={{ objectFit: 'cover' }} width={40} height={40} alt={'Logo'} /> <span>Have Small Bytes</span> </Link> </div> {/* 嵌入客户端组件,传递服务端获取的数据 */} <ResponsiveNav categories={categories} /> </header> ) } export default Header
额外优化建议
如果只是样式层面的适配(比如隐藏/显示元素、调整布局),优先使用CSS媒体查询,无需借助JS监听窗口尺寸,性能更优且实现简单。例如在你的SCSS文件中添加:
.nav { // 桌面端样式 display: flex; gap: 1rem; @media (max-width: 768px) { // 移动端隐藏默认导航 display: none; } } .mobileMenuBtn { display: none; @media (max-width: 768px) { // 移动端显示汉堡按钮 display: block; } }
内容的提问来源于stack exchange,提问作者Kartik Thakur
相关产品推荐
相关产品推荐

