使用Webpack预渲染React到HTML模板时遇window未定义错误求助
解决Webpack+React预渲染时的
window is not defined错误 这个问题我之前也碰到过,核心是预渲染代码在Node.js环境执行,但组件里访问了浏览器独有的window对象,再加上你在EJS模板里直接require预渲染模块的方式也有点问题,咱们一步步来解决:
为什么会报错?
当HtmlWebpackPlugin处理你的EJS模板时,它是在Node.js(服务端)环境中运行的,而不是浏览器环境:
- 你的
App组件里可能有直接在顶层访问window的代码(比如const width = window.innerWidth),Node环境没有这个全局对象,直接报错。 - 你在EJS模板里写
<%= require('../appPrerender.js') %>,相当于让Node直接执行这个模块,没有经过Webpack的客户端打包处理,组件里的浏览器代码直接在服务端跑,自然出问题。
解决方案1:修复组件中的window访问逻辑
先检查你的App.js,如果有直接在组件顶层使用window的代码,改成组件挂载后再访问,或者做环境判断:
示例:用React Hooks处理浏览器API
// client/app.js import { useEffect, useState } from 'react'; function App() { const [windowWidth, setWindowWidth] = useState(0); useEffect(() => { // 组件挂载到浏览器后,再访问window const updateWidth = () => setWindowWidth(window.innerWidth); updateWidth(); // 监听窗口变化 window.addEventListener('resize', updateWidth); // 组件卸载时清理监听 return () => window.removeEventListener('resize', updateWidth); }, []); return <div>当前窗口宽度:{windowWidth}</div>; } export default App;
示例:环境判断兼容服务端
如果必须在组件顶层使用,加个环境判断:
// client/app.js const initialWidth = typeof window !== 'undefined' ? window.innerWidth : 0; function App() { const [width, setWidth] = useState(initialWidth); // ...后续逻辑 }
解决方案2:调整预渲染的执行方式(推荐)
不要在EJS模板里直接require预渲染模块,换一种方式把预渲染内容注入到HTML中:
方法A:在Webpack配置中直接生成预渲染内容
修改你的webpack.common.js,让HtmlWebpackPlugin直接调用预渲染逻辑,而不是依赖EJS模板里的require:
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const React = require('react'); const ReactDOMServer = require('react-dom/server'); // 注意这里要正确引入你的App组件(如果用ES模块导出,要加.default) const App = require('./client/app').default; module.exports = { // ...其他Webpack配置 plugins: [ new HtmlWebpackPlugin({ templateContent: () => { // 在Node环境中执行预渲染 const prerenderedHtml = ReactDOMServer.renderToString(React.createElement(App)); // 返回完整的HTML模板 return ` <!DOCTYPE html> <html> <head> <title>Anon's Website</title> </head> <body> <div id="root">${prerenderedHtml}</div> </body> </html> `.trim(); } }) ] };
方法B:单独预渲染生成静态片段
如果你的预渲染逻辑比较复杂,可以单独写脚本生成静态HTML,再让模板读取:
- 创建
prerender.js脚本:
// 项目根目录/prerender.js const fs = require('fs'); const path = require('path'); const React = require('react'); const ReactDOMServer = require('react-dom/server'); const App = require('./client/app').default; // 生成预渲染HTML const prerenderedHtml = ReactDOMServer.renderToString(React.createElement(App)); // 写入静态文件 fs.writeFileSync(path.join(__dirname, 'client/public/prerendered.html'), prerenderedHtml);
- 修改
package.json的脚本,先预渲染再打包:
{ "scripts": { "prerender": "node prerender.js", "build": "npm run prerender && webpack --config webpack.common.js" } }
- 修改EJS模板,读取生成的静态文件:
<!DOCTYPE html> <html> <head> <title>Anon's Website</title> </head> <body> <div id="root"><%= require('fs').readFileSync(path.join(__dirname, '../client/public/prerendered.html'), 'utf8') %></div> </body> </html>
额外注意点
- 确保你的React组件是同构友好的:避免在组件顶层使用浏览器特有的API(
window、document、localStorage等),尽量在组件生命周期或Hooks中使用。 - 如果用了第三方库,要确认它们支持服务端渲染,有些库可能需要额外配置(比如
styled-components需要用babel-plugin-styled-components支持SSR)。
内容的提问来源于stack exchange,提问作者Lilith
相关产品推荐
相关产品推荐

