React/Gatsby生产环境使用postscribe报window未定义如何解决
你加的window !== undefined判断只覆盖了useEffect执行阶段的window访问,根本拦不住报错:Gatsby生产构建会在Node.js服务端渲染(SSR)环境执行模块的静态导入逻辑,而你在文件顶部写的import postscribe from 'postscribe'会在模块初始化阶段就加载postscribe库——这个库本身顶层代码就会直接访问window对象,Node环境没有这个对象,代码在导入阶段就直接抛错,根本等不到组件渲染、useEffect里的判断逻辑执行。
本地开发环境没报错是因为gatsby develop的SSR校验比生产构建宽松,不会提前触发这类第三方库的浏览器API访问,属于开发环境和生产环境行为不一致的典型场景。
核心思路是把postscribe的加载逻辑完全挪到客户端侧,绕开SSR构建阶段,两种改动量很小的实现方式:
方案1:useEffect内动态导入(推荐)
删掉文件顶部对postscribe的静态导入,把导入逻辑移到useEffect里用动态import实现,动态导入只会在客户端执行副作用时触发,完全不会在SSR阶段运行:
import { useEffect, useRef } from 'react' // 删掉顶部静态导入:import postscribe from 'postscribe' const FormEmbed = () => { const advRef = useRef(null) useEffect(() => { if (!advRef.current) return // 客户端侧才加载postscribe import('postscribe').then(({ default: postscribe }) => { postscribe( advRef.current, "<script src='...替换为你的Formstack表单地址...'><\/script>" ) }) }, [advRef]) return <div id="adv" ref={advRef}></div> } export default FormEmbed
方案2:标记组件为仅客户端渲染
如果动态导入后仍有兼容问题,可以直接把整个表单嵌入组件标记为仅在客户端渲染,从根源上避免SSR执行该组件的任何代码:
- 把上面写的表单嵌入组件单独存为
src/components/FormstackForm.js - 在需要引入表单的页面/组件中,用Gatsby默认支持的loadable做客户端懒加载:
import loadable from '@loadable/component' // SSR阶段完全跳过该组件的加载和渲染 const FormstackForm = loadable(() => import('../components/FormstackForm')) const TargetPage = () => { return ( <main> {/* 其他页面内容 */} <FormstackForm /> </main> ) }
所有依赖浏览器全局API(window、document等)的第三方库,都不要在Gatsby/Next这类支持SSR的框架里用顶层静态import引入,只要导入语句在模块顶层,构建阶段就会在Node环境执行库的初始化代码,很容易触发这类window is not defined报错。
内容的提问来源于stack exchange,提问作者yankeedoodle

