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

如何解决Gatsby SSR后useState窗口尺寸导致的根页渲染问题

Gatsby SSR场景下窗口尺寸Hook首屏渲染异常问题

问题现象

在Gatsby项目中执行gatsby clean && gatsby build && gatsby serve做部署测试时,访问站点根路径(https://foobar.com/),所有依赖ThemeProvider获取尺寸参数的组件均无法正常渲染。经调试定位,问题根源为useState初始化时硬编码的窗口尺寸默认值为0,相关初始代码片段如下:

const [size, setSize] = useState({
    windowWidth: hasWindow ? window.innerWidth : 0,
    windowHeight: hasWindow ? window.innerHeight : 0,
  })

相关问题代码

自定义窗口尺寸Hook完整实现

import { useState, useEffect } from 'react'

const hasWindow = typeof window !== 'undefined'

const useWindowDimensions = () => {
  const [size, setSize] = useState({
    windowWidth: hasWindow ? window.innerWidth : 0,
    windowHeight: hasWindow ? window.innerHeight : 0,
  })

  const updateSize = () =>
    setSize({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight,
    })

  useEffect(() => (window.onresize = updateSize), [])

  return size
}

export default useWindowDimensions

ThemeProvider上下文实现

Hook返回的尺寸值会直接传递给ThemeProvider上下文,相关代码如下:

import React, { createContext } from 'react'

// Utils
import useWindowDimensions from '../utils/useWindowDimensions'

const defaultContext = {
  windowWidth: 0,
  windowHeight: 0,
}

export const ThemeContext = createContext(defaultContext)

const ThemeProvider = ({ children }) => {
  const { windowWidth, windowHeight } = useWindowDimensions()

  return (
    <ThemeContext.Provider value={{ windowWidth, windowHeight }}>{children}</ThemeContext.Provider>
  )
}

export default ThemeProvider

已验证的无效方案

  • 硬编码服务端fallback值为1600
    对应修改代码:

    const [size, setSize] = useState({
        windowWidth: hasWindow ? window.innerWidth : 1600,
        windowHeight: hasWindow ? window.innerHeight : 1600,
      })
    

    实际效果:桌面端浏览器可正常渲染,但移动端、窗口宽度小于1600px的场景下取值错误;只有跳转至站点其他路由触发useState值更新后,渲染异常问题才会消失。

  • 移除window存在性判断直接读取属性
    对应修改代码:

    const [size, setSize] = useState({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight,
    })
    

    实际效果:执行gatsby build时会触发符合预期的服务端渲染报错:

    "window" is not available during server side rendering.

解决方案

问题核心原因是原Hook仅在窗口resize事件触发时才更新尺寸值,客户端hydration完成后没有主动同步真实窗口尺寸,导致首屏一直使用SSR阶段的初始默认值渲染。修复后的Hook实现如下:

import { useState, useEffect } from 'react'

const hasWindow = typeof window !== 'undefined'
const getInitialSize = () => ({
  windowWidth: hasWindow ? window.innerWidth : 0,
  windowHeight: hasWindow ? window.innerHeight : 0,
})

const useWindowDimensions = () => {
  const [size, setSize] = useState(getInitialSize)

  const updateSize = () => {
    if (!hasWindow) return
    setSize({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight,
    })
  }

  useEffect(() => {
    // 客户端hydration完成后立刻同步一次真实窗口尺寸,解决首屏值错误问题
    updateSize()
    // 改用标准事件监听绑定resize,避免覆盖全局其他resize回调
    window.addEventListener('resize', updateSize)
    // 组件卸载时移除监听,避免内存泄漏
    return () => window.removeEventListener('resize', updateSize)
  }, [])

  return size
}

export default useWindowDimensions

修复说明

  • 核心逻辑:useEffect内的代码仅在客户端执行,不会触发SSR报错。组件挂载后主动调用一次updateSize,会立刻读取真实窗口尺寸更新state,首屏hydration阶段就能拿到正确值,不需要等resize事件或路由跳转触发更新。
  • 原代码直接给window.onresize赋值的写法会覆盖全局其他位置绑定的resize事件回调,改用addEventListener/removeEventListener的标准写法更稳妥。
  • 如果想减少hydration前后值差异导致的短暂布局抖动,可以把SSR阶段的初始fallback值改成项目受众最常用的设备尺寸(比如桌面端项目设为1920,移动端占比高设为375),这个值仅在SSR输出静态HTML、hydration完成前的极短时间生效,最终会被客户端读取到的真实值覆盖,不会出现硬编码固定值导致的小屏适配错误。

内容的提问来源于stack exchange,提问作者GʀᴜᴍᴘʏCᴀᴛ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 17:25:33