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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 13:10:57