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

如何解决Gatsby构建window undefined及React Hook条件调用报错

问题修复方案

你遇到的两个报错根本原因是违反了React Hook的调用规则:所有Hook必须在组件的最顶层调用,不能包裹在条件判断语句中。你将useWindowSize和useEffect放在if (typeof window !== "undefined")的条件分支里,构建阶段和客户端渲染时Hook的调用顺序不一致,就触发了该错误。

Gatsby构建阶段只会执行组件的同步渲染逻辑,不会执行useEffect的回调,所有涉及window、document的浏览器API操作都放到useEffect回调中执行,天然就避开了构建阶段不存在window的问题,不需要在Hook调用层加条件判断。

修正后的完整代码如下:

import * as React from "react"

//Library
import { useEffect, useState } from "react"
import { useStaticQuery, graphql } from "gatsby"
import loadable from "@loadable/component"

//Components
import { Header } from "./header"

import { SkipNavContent, SkipNavLink } from "./skip-nav"
import { Seo } from "./seo"
import Toast from "./toast"

//Utils
import { StoreContext } from "../context/store-context"
import { isBrowser, useWindowSize } from "../utils/helpers"

const CartSidebar = loadable(() => import("./cart/CartSidebar"))
const Newsletter = loadable(() => import("./newsletter"))
const Footer = loadable(() => import("./footer"))

export function Layout({ children }) {
  // 所有Hook放在组件最顶层,符合调用规则
  const { loading, didJustAddToCart } = React.useContext(StoreContext)
  const [showLazySections, setShowLazySections] = React.useState(false)
  const [headerHeight, setHeaderHeight] = useState(null)
  // 直接调用useWindowSize,不要包条件,Hook内部兼容SSR返回默认值即可
  const { height: windowHeight } = useWindowSize()

  // 补上你遗漏的siteTitle静态查询
  const { site } = useStaticQuery(graphql`
    query SiteTitleQuery {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)
  const siteTitle = site.siteMetadata.title

  // 动态设置vh变量,useEffect仅在客户端执行,天然避开构建阶段window问题
  useEffect(() => {
    if (isBrowser) {
      document.body.style.setProperty("--vh", `${windowHeight * 0.01}px`)
    }
  }, [windowHeight])

  useEffect(() => {
    setShowLazySections(true)
  }, [])

  return (
    <div
      style={headerHeight ? { "--headerHeight": `${headerHeight}px` } : null}
    >
      <SkipNavLink />
      <Seo />
      <Header onSetup={({ height }) => setHeaderHeight(height)} />
      <SkipNavContent>{children}</SkipNavContent>
      {showLazySections && (
        <>
          <CartSidebar />
          <Newsletter />
          <Footer siteTitle={siteTitle} />
        </>
      )}
    </div>
  )
}

如果你的useWindowSize还没有做SSR兼容,内部直接引用window导致构建报错,可以修改为如下兼容写法:

// utils/helpers.js 中的useWindowSize调整为
export function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: 0,
    height: 0,
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    window.addEventListener("resize", handleResize);
    handleResize();
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return windowSize;
}

该写法服务端渲染时默认返回宽高为0,客户端挂载后自动更新为真实值,不会触发构建报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 15:15:05