NextJS v11.1.12报ReferenceError: document is not defined的原因及解决方法询问
报错原因
NextJS默认启用服务端渲染(SSR)/静态站点生成(SSG)能力,代码会先在Node.js服务端环境执行预渲染逻辑,而Node环境中不存在浏览器专属的document、window等全局对象,你直接在组件顶层作用域调用document.querySelector,就会触发ReferenceError: document is not defined报错。
至于之前代码可以正常运行,大概率是此前你临时关闭了对应组件的SSR,或是代码逻辑原本只会在客户端交互时触发,后续缓存或构建规则变动导致这部分DOM操作逻辑被提前到了服务端执行。
另外你原代码的document.querySelector('state-select-dropdown-box')存在语法问题:缺少类选择器的.前缀,会被识别为选择<state-select-dropdown-box>标签,即使解决了document报错也无法选中对应类名的div,需要修改为.state-select-dropdown-box。
解决方案
方案1:将DOM操作放入useEffect钩子(最推荐)
React的useEffect回调只会在客户端组件挂载完成后执行,此时可以正常访问浏览器全局对象,同时更推荐用useRef直接绑定目标DOM,比querySelector更符合React开发规范:
import { useEffect, useRef } from 'react' const Header = () => { // 用useRef绑定目标DOM元素 const dropDownRef = useRef(null) function toggleMenu() { // 可选链避免空值报错 dropDownRef.current?.classList.toggle('show') } // 如果你一定要用querySelector可以写在useEffect内部 useEffect(() => { const dropDownMenu = document.querySelector('.state-select-dropdown-box') console.log(dropDownMenu) }, []) return ( <div> {/* 给目标div绑定ref */} <div ref={dropDownRef} className="state-select-dropdown-box"> {/* 下拉菜单内容 */} </div> <button onClick={toggleMenu}>切换菜单</button> </div> ) }
方案2:前置判断浏览器环境
如果不想修改hooks逻辑,可以在调用document前先判断当前是否为浏览器环境:
const dropDownMenu = typeof window !== 'undefined' ? document.querySelector('.state-select-dropdown-box') : null
注意该方式下服务端渲染阶段拿到的dropDownMenu为null,需要保证调用toggleMenu的逻辑只会在客户端交互时触发。
方案3:关闭组件的SSR能力
如果整个组件都不需要参与服务端渲染,可以用NextJS提供的dynamic方法导入组件并关闭SSR:
// 在引入Header组件的父组件中编写 import dynamic from 'next/dynamic' const Header = dynamic(() => import('./Header'), { ssr: false })
如果是使用Next 13+的App Router,直接在Header组件顶部加上'use client'指令即可标记为客户端组件,直接访问document不会报错。
内容的提问来源于stack exchange,提问作者S Hawley

